Files
visor/test/syscalls/linux/splice.cc
T
Nicolas LacasseandgVisor bot 15f268b4f0 Fix infinite loop when splicing to pipes/eventfds.
Writes to pipes of size < PIPE_BUF are guaranteed to be atomic, so writes
larger than that will return EAGAIN if the pipe has capacity < PIPE_BUF.

Writes to eventfds will return EAGAIN if the write would cause the eventfd
value to go over the max.

In both such cases, calling Ready() on the FD will return true (because it is
possible to write), but specific kinds of writes will in fact return EAGAIN.

This CL fixes an infinite loop in splice and sendfile (VFS1 and VFS2) by
forcing skipping the readiness check for the outfile in send, splice, and tee.

PiperOrigin-RevId: 341102260
2020-11-06 12:55:29 -08:00

834 lines
29 KiB
C++

// 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