// 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/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_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, 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 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.
  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, 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);
}

}  // namespace

}  // namespace testing
}  // namespace gvisor