diff options
Diffstat (limited to 'test/syscalls/linux/splice.cc')
-rw-r--r-- | test/syscalls/linux/splice.cc | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc new file mode 100644 index 000000000..1875f4533 --- /dev/null +++ b/test/syscalls/linux/splice.cc @@ -0,0 +1,404 @@ +// 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 <sys/sendfile.h> +#include <unistd.h> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.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 inf = + ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); + + // Open the output file as write only. + const FileDescriptor outf = + 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(inf.get(), &in_offset, outf.get(), &out_offset, 1, 0), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(splice(inf.get(), nullptr, outf.get(), &out_offset, 1, 0), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(splice(inf.get(), &in_offset, outf.get(), nullptr, 1, 0), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(splice(inf.get(), nullptr, outf.get(), nullptr, 1, 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) { + SKIP_IF(IsRunningOnGvisor()); + + // 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) { + SKIP_IF(IsRunningOnGvisor()); + + // Open some file. + const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor inf = + 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(inf.get(), wfd.get(), kPageSize, 0), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(tee(rfd.get(), inf.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)); +} + +TEST(SpliceTest, ToPipe) { + // Open the input file. + const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor inf = + 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(inf.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(kPageSize)); + ASSERT_THAT(lseek(inf.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(inf.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 inf = + 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(inf.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(inf.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 outf = + ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); + + // Splice to the output file. + EXPECT_THAT(splice(rfd.get(), nullptr, outf.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(outf.get(), 0, SEEK_CUR), + SyscallSucceedsWithValue(kPageSize)); + ASSERT_THAT(lseek(outf.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); + + // Contents should be equal. + std::vector<char> rbuf(kPageSize); + ASSERT_THAT(read(outf.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 outf = + 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, outf.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(outf.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, 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) { + SKIP_IF(IsRunningOnGvisor()); + + // 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(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) { + SKIP_IF(IsRunningOnGvisor()); + + // 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)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor |