summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/splice.cc
diff options
context:
space:
mode:
authorNicolas Lacasse <nlacasse@google.com>2020-11-06 12:53:49 -0800
committergVisor bot <gvisor-bot@google.com>2020-11-06 12:55:29 -0800
commit53eeb06ef14915eee799e9d7d59603ed2a0fe1c1 (patch)
tree038f56ca3f0c464d61a4b2ca6f482127c01c613c /test/syscalls/linux/splice.cc
parent955e09dfbdb8a4cdae0a0b625001a567f6f15758 (diff)
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
Diffstat (limited to 'test/syscalls/linux/splice.cc')
-rw-r--r--test/syscalls/linux/splice.cc55
1 files changed, 55 insertions, 0 deletions
diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc
index a1d2b9b11..c2369db54 100644
--- a/test/syscalls/linux/splice.cc
+++ b/test/syscalls/linux/splice.cc
@@ -26,9 +26,11 @@
#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 {
@@ -772,6 +774,59 @@ TEST(SpliceTest, FromPipeToDevZero) {
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