diff options
Diffstat (limited to 'test/syscalls/linux/splice.cc')
-rw-r--r-- | test/syscalls/linux/splice.cc | 833 |
1 files changed, 0 insertions, 833 deletions
diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc deleted file mode 100644 index c2369db54..000000000 --- a/test/syscalls/linux/splice.cc +++ /dev/null @@ -1,833 +0,0 @@ -// Copyright 2019 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 <linux/unistd.h> -#include <sys/eventfd.h> -#include <sys/resource.h> -#include <sys/sendfile.h> -#include <sys/time.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/signal_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SpliceTest, TwoRegularFiles) { - // Create temp files. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Verify that it is rejected as expected; regardless of offsets. - loff_t in_offset = 0; - loff_t out_offset = 0; - EXPECT_THAT(splice(in_fd.get(), &in_offset, out_fd.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(splice(in_fd.get(), nullptr, out_fd.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(splice(in_fd.get(), &in_offset, out_fd.get(), nullptr, 1, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(splice(in_fd.get(), nullptr, out_fd.get(), nullptr, 1, 0), - SyscallFailsWithErrno(EINVAL)); -} - -int memfd_create(const std::string& name, unsigned int flags) { - return syscall(__NR_memfd_create, name.c_str(), flags); -} - -TEST(SpliceTest, NegativeOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the output file as write only. - int fd; - EXPECT_THAT(fd = memfd_create("negative", 0), SyscallSucceeds()); - const FileDescriptor out_fd(fd); - - loff_t out_offset = 0xffffffffffffffffull; - constexpr int kSize = 2; - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Write offset + size overflows int64. -// -// This is a regression test for b/148041624. -TEST(SpliceTest, WriteOverflow) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the output file. - int fd; - EXPECT_THAT(fd = memfd_create("overflow", 0), SyscallSucceeds()); - const FileDescriptor out_fd(fd); - - // out_offset + kSize overflows INT64_MAX. - loff_t out_offset = 0x7ffffffffffffffeull; - constexpr int kSize = 3; - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SpliceTest, SamePipe) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Attempt to splice to itself. - EXPECT_THAT(splice(rfd.get(), nullptr, wfd.get(), nullptr, kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(TeeTest, SamePipe) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Attempt to tee to itself. - EXPECT_THAT(tee(rfd.get(), wfd.get(), kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(TeeTest, RegularFile) { - // Open some file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Attempt to tee from the file. - EXPECT_THAT(tee(in_fd.get(), wfd.get(), kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(tee(rfd.get(), in_fd.get(), kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SpliceTest, PipeOffsets) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // All pipe offsets should be rejected. - loff_t in_offset = 0; - loff_t out_offset = 0; - EXPECT_THAT(splice(rfd1.get(), &in_offset, wfd2.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(splice(rfd1.get(), &in_offset, wfd2.get(), nullptr, 1, 0), - SyscallFailsWithErrno(ESPIPE)); -} - -// Event FDs may be used with splice without an offset. -TEST(SpliceTest, FromEventFD) { - // Open the input eventfd with an initial value so that it is readable. - constexpr uint64_t kEventFDValue = 1; - int efd; - ASSERT_THAT(efd = eventfd(kEventFDValue, 0), SyscallSucceeds()); - const FileDescriptor in_fd(efd); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice 8-byte eventfd value to pipe. - constexpr int kEventFDSize = 8; - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kEventFDSize, 0), - SyscallSucceedsWithValue(kEventFDSize)); - - // Contents should be equal. - std::vector<char> rbuf(kEventFDSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kEventFDSize)); - EXPECT_EQ(memcmp(rbuf.data(), &kEventFDValue, rbuf.size()), 0); -} - -// Event FDs may not be used with splice with an offset. -TEST(SpliceTest, FromEventFDOffset) { - int efd; - ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds()); - const FileDescriptor in_fd(efd); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Attempt to splice 8-byte eventfd value to pipe with offset. - // - // This is not allowed because eventfd doesn't support pread. - constexpr int kEventFDSize = 8; - loff_t in_off = 0; - EXPECT_THAT(splice(in_fd.get(), &in_off, wfd.get(), nullptr, kEventFDSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Event FDs may not be used with splice with an offset. -TEST(SpliceTest, ToEventFDOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with a value. - constexpr int kEventFDSize = 8; - std::vector<char> buf(kEventFDSize); - buf[0] = 1; - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kEventFDSize)); - - int efd; - ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds()); - const FileDescriptor out_fd(efd); - - // Attempt to splice 8-byte eventfd value to pipe with offset. - // - // This is not allowed because eventfd doesn't support pwrite. - loff_t out_off = 0; - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), &out_off, kEventFDSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SpliceTest, ToPipe) { - // Open the input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(in_fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(lseek(in_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice to the pipe. - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Contents should be equal. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST(SpliceTest, ToPipeEOF) { - // Create and open an empty input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice from the empty file to the pipe. - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, 123, 0), - SyscallSucceedsWithValue(0)); -} - -TEST(SpliceTest, ToPipeOffset) { - // Open the input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(in_fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice to the pipe. - loff_t in_offset = kPageSize / 2; - EXPECT_THAT( - splice(in_fd.get(), &in_offset, wfd.get(), nullptr, kPageSize / 2, 0), - SyscallSucceedsWithValue(kPageSize / 2)); - - // Contents should be equal to only the second part. - std::vector<char> rbuf(kPageSize / 2); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize / 2)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data() + (kPageSize / 2), rbuf.size()), 0); -} - -TEST(SpliceTest, FromPipe) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the output file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Splice to the output file. - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // The offset of the output should be equal to kPageSize. We assert that and - // reset to zero so that we can read the contents and ensure they match. - EXPECT_THAT(lseek(out_fd.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(lseek(out_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Contents should be equal. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST(SpliceTest, FromPipeMultiple) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - std::string buf = "abcABC123"; - ASSERT_THAT(write(wfd.get(), buf.c_str(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Open the output file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Splice from the pipe to the output file over several calls. - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3, 0), - SyscallSucceedsWithValue(3)); - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3, 0), - SyscallSucceedsWithValue(3)); - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3, 0), - SyscallSucceedsWithValue(3)); - - // Reset cursor to zero so that we can check the contents. - ASSERT_THAT(lseek(out_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Contents should be equal. - std::vector<char> rbuf(buf.size()); - ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), buf.c_str(), buf.size()), 0); -} - -TEST(SpliceTest, FromPipeOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the input file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Splice to the output file. - loff_t out_offset = kPageSize / 2; - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Content should reflect the splice. We write to a specific offset in the - // file, so the internals should now be allocated sparsely. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - std::vector<char> zbuf(kPageSize / 2); - memset(zbuf.data(), 0, zbuf.size()); - EXPECT_EQ(memcmp(rbuf.data(), zbuf.data(), zbuf.size()), 0); - EXPECT_EQ(memcmp(rbuf.data() + kPageSize / 2, buf.data(), kPageSize / 2), 0); -} - -TEST(SpliceTest, TwoPipes) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Splice to the second pipe, using two operations. - EXPECT_THAT( - splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize / 2, 0), - SyscallSucceedsWithValue(kPageSize / 2)); - EXPECT_THAT( - splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize / 2, 0), - SyscallSucceedsWithValue(kPageSize / 2)); - - // Content should reflect the splice. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); -} - -TEST(SpliceTest, TwoPipesCircular) { - // This test deadlocks the sentry on VFS1 because VFS1 splice ordering is - // based on fs.File.UniqueID, which does not prevent circular ordering between - // e.g. inode-level locks taken by fs.FileOperations. - SKIP_IF(IsRunningWithVFS1()); - - // Create two pipes. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor first_rfd(fds[0]); - const FileDescriptor first_wfd(fds[1]); - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor second_rfd(fds[0]); - const FileDescriptor second_wfd(fds[1]); - - // On Linux, each pipe is normally limited to - // include/linux/pipe_fs_i.h:PIPE_DEF_BUFFERS buffers worth of data. - constexpr size_t PIPE_DEF_BUFFERS = 16; - - // Write some data to each pipe. Below we splice 1 byte at a time between - // pipes, which very quickly causes each byte to be stored in a separate - // buffer, so we must ensure that the total amount of data in the system is <= - // PIPE_DEF_BUFFERS bytes. - std::vector<char> buf(PIPE_DEF_BUFFERS / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - ASSERT_THAT(write(second_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Have another thread splice from the second pipe to the first, while we - // splice from the first to the second. The test passes if this does not - // deadlock. - const int kIterations = 1000; - DisableSave ds; - ScopedThread t([&]() { - for (int i = 0; i < kIterations; i++) { - ASSERT_THAT( - splice(second_rfd.get(), nullptr, first_wfd.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(1)); - } - }); - for (int i = 0; i < kIterations; i++) { - ASSERT_THAT( - splice(first_rfd.get(), nullptr, second_wfd.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(1)); - } -} - -TEST(SpliceTest, Blocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // This thread writes to the main pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ScopedThread t([&]() { - ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - }); - - // Attempt a splice immediately; it should block. - EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Thread should be joinable. - t.Join(); - - // Content should reflect the splice. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); -} - -TEST(TeeTest, Blocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // This thread writes to the main pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ScopedThread t([&]() { - ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - }); - - // Attempt a tee immediately; it should block. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Thread should be joinable. - t.Join(); - - // Content should reflect the splice, in both pipes. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); - ASSERT_THAT(read(rfd1.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); -} - -TEST(TeeTest, BlockingWrite) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Make some data available to be read. - std::vector<char> buf1(kPageSize); - RandomizeBuffer(buf1.data(), buf1.size()); - ASSERT_THAT(write(wfd1.get(), buf1.data(), buf1.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Fill up the write pipe's buffer. - int pipe_size = -1; - ASSERT_THAT(pipe_size = fcntl(wfd2.get(), F_GETPIPE_SZ), SyscallSucceeds()); - std::vector<char> buf2(pipe_size); - ASSERT_THAT(write(wfd2.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(pipe_size)); - - ScopedThread t([&]() { - absl::SleepFor(absl::Milliseconds(100)); - ASSERT_THAT(read(rfd2.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(pipe_size)); - }); - - // Attempt a tee immediately; it should block. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Thread should be joinable. - t.Join(); - - // Content should reflect the tee. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf1.data(), kPageSize), 0); -} - -TEST(SpliceTest, NonBlocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Splice with no data to back it. - EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize, - SPLICE_F_NONBLOCK), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(TeeTest, NonBlocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Splice with no data to back it. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, SPLICE_F_NONBLOCK), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(TeeTest, MultiPage) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Make some data available to be read. - std::vector<char> wbuf(8 * kPageSize); - RandomizeBuffer(wbuf.data(), wbuf.size()); - ASSERT_THAT(write(wfd1.get(), wbuf.data(), wbuf.size()), - SyscallSucceedsWithValue(wbuf.size())); - - // Attempt a tee immediately; it should complete. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), wbuf.size(), 0), - SyscallSucceedsWithValue(wbuf.size())); - - // Content should reflect the tee. - std::vector<char> rbuf(wbuf.size()); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), rbuf.size()), 0); - ASSERT_THAT(read(rfd1.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), rbuf.size()), 0); -} - -TEST(SpliceTest, FromPipeMaxFileSize) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the input file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - EXPECT_THAT(ftruncate(out_fd.get(), 13 << 20), SyscallSucceeds()); - EXPECT_THAT(lseek(out_fd.get(), 0, SEEK_END), - SyscallSucceedsWithValue(13 << 20)); - - // Set our file size limit. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGXFSZ); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - rlimit rlim = {}; - rlim.rlim_cur = rlim.rlim_max = (13 << 20); - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &rlim), SyscallSucceeds()); - - // Splice to the output file. - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3 * kPageSize, 0), - SyscallFailsWithErrno(EFBIG)); - - // Contents should be equal. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST(SpliceTest, FromPipeToDevZero) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - const FileDescriptor zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY)); - - // Close the write end to prevent blocking below. - wfd.reset(); - - // Splice to /dev/zero. The first call should empty the pipe, and the return - // value should not exceed the number of bytes available for reading. - EXPECT_THAT( - splice(rfd.get(), nullptr, zero.get(), nullptr, kPageSize + 123, 0), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_THAT(splice(rfd.get(), nullptr, zero.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(0)); -} - -static volatile int signaled = 0; -void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } - -TEST(SpliceTest, ToPipeWithSmallCapacityDoesNotSpin_NoRandomSave) { - // Writes to a pipe that are less than PIPE_BUF must be atomic. This test - // creates a pipe with only 128 bytes of capacity (< PIPE_BUF) and checks that - // splicing to the pipe does not spin. See b/170743336. - - // Create a file with one page of data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(buf.data(), buf.size()), - TempPath::kDefaultFileMode)); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - // Create a pipe with size 4096, and fill all but 128 bytes of it. - int p[2]; - ASSERT_THAT(pipe(p), SyscallSucceeds()); - ASSERT_THAT(fcntl(p[1], F_SETPIPE_SZ, kPageSize), SyscallSucceeds()); - const int kWriteSize = kPageSize - 128; - std::vector<char> writeBuf(kWriteSize); - RandomizeBuffer(writeBuf.data(), writeBuf.size()); - ASSERT_THAT(write(p[1], writeBuf.data(), writeBuf.size()), - SyscallSucceedsWithValue(kWriteSize)); - - // Set up signal handler. - struct sigaction sa = {}; - sa.sa_sigaction = SigUsr1Handler; - sa.sa_flags = SA_SIGINFO; - const auto cleanup_sigact = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); - - // Send SIGUSR1 to this thread in 1 second. - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = SIGUSR1; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(absl::Seconds(1)); - DisableSave ds; // Asserting an EINTR. - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // Now splice the file to the pipe. This should block, but not spin, and - // should return EINTR because it is interrupted by the signal. - EXPECT_THAT(splice(fd.get(), nullptr, p[1], nullptr, kPageSize, 0), - SyscallFailsWithErrno(EINTR)); - - // Alarm should have been handled. - EXPECT_EQ(signaled, 1); -} - -} // namespace - -} // namespace testing -} // namespace gvisor |