summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/sendfile_socket.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/sendfile_socket.cc')
-rw-r--r--test/syscalls/linux/sendfile_socket.cc156
1 files changed, 156 insertions, 0 deletions
diff --git a/test/syscalls/linux/sendfile_socket.cc b/test/syscalls/linux/sendfile_socket.cc
new file mode 100644
index 000000000..7010dc211
--- /dev/null
+++ b/test/syscalls/linux/sendfile_socket.cc
@@ -0,0 +1,156 @@
+// Copyright 2018 Google LLC
+//
+// 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 <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/sendfile.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
+#include "test/syscalls/linux/socket_test_util.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 {
+
+// Sends large file to exercise the path that read and writes data multiple
+// times, esp. when more data is read than can be written.
+TEST(SendFileTest, SendMultiple) {
+ std::vector<char> data(5 * 1024 * 1024);
+ RandomizeBuffer(data.data(), data.size());
+
+ // Create temp files.
+ const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
+ GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()),
+ TempPath::kDefaultFileMode));
+ const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+
+ // Use a socket for target file to make the write window small.
+ const FileDescriptor server(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(server.get(), SyscallSucceeds());
+
+ struct sockaddr_in server_addr = {};
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = INADDR_ANY;
+ ASSERT_THAT(
+ bind(server.get(), reinterpret_cast<struct sockaddr *>(&server_addr),
+ sizeof(server_addr)),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(server.get(), 1), SyscallSucceeds());
+
+ // Thread that reads data from socket and dumps to a file.
+ ScopedThread th([&server, &out_file, &server_addr] {
+ socklen_t addrlen = sizeof(server_addr);
+ const FileDescriptor fd(RetryEINTR(accept)(
+ server.get(), reinterpret_cast<struct sockaddr *>(&server_addr),
+ &addrlen));
+ ASSERT_THAT(fd.get(), SyscallSucceeds());
+
+ FileDescriptor outf =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
+
+ // Read until socket is closed.
+ char buf[10240];
+ for (int cnt = 0;; cnt++) {
+ int r = RetryEINTR(read)(fd.get(), buf, sizeof(buf));
+ // We cannot afford to save on every read() call.
+ if (cnt % 1000 == 0) {
+ ASSERT_THAT(r, SyscallSucceeds());
+ } else {
+ const DisableSave ds;
+ ASSERT_THAT(r, SyscallSucceeds());
+ }
+ if (r == 0) {
+ // EOF
+ break;
+ }
+ int w = RetryEINTR(write)(outf.get(), buf, r);
+ // We cannot afford to save on every write() call.
+ if (cnt % 1010 == 0) {
+ ASSERT_THAT(w, SyscallSucceedsWithValue(r));
+ } else {
+ const DisableSave ds;
+ ASSERT_THAT(w, SyscallSucceedsWithValue(r));
+ }
+ }
+ });
+
+ // Open the input file as read only.
+ const FileDescriptor inf =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
+
+ FileDescriptor outf(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(outf.get(), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = sizeof(server_addr);
+ ASSERT_THAT(getsockname(server.get(),
+ reinterpret_cast<sockaddr *>(&server_addr), &addrlen),
+ SyscallSucceeds());
+
+ struct sockaddr_in addr = {};
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr("127.0.0.1");
+ addr.sin_port = server_addr.sin_port;
+ LOG(INFO) << "Connecting on port=" << server_addr.sin_port;
+ ASSERT_THAT(
+ RetryEINTR(connect)(
+ outf.get(), reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)),
+ SyscallSucceeds());
+
+ int cnt = 0;
+ for (size_t sent = 0; sent < data.size(); cnt++) {
+ const size_t remain = data.size() - sent;
+ LOG(INFO) << "sendfile, size=" << data.size() << ", sent=" << sent
+ << ", remain=" << remain;
+
+ // Send data and verify that sendfile returns the correct value.
+ int res = sendfile(outf.get(), inf.get(), nullptr, remain);
+ // We cannot afford to save on every sendfile() call.
+ if (cnt % 120 == 0) {
+ MaybeSave();
+ }
+ if (res == 0) {
+ // EOF
+ break;
+ }
+ if (res > 0) {
+ sent += res;
+ } else {
+ ASSERT_TRUE(errno == EINTR || errno == EAGAIN) << "errno=" << errno;
+ }
+ }
+
+ // Close socket to stop thread.
+ outf.reset();
+ th.Join();
+
+ // Verify that the output file has the correct data.
+ outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
+ std::vector<char> actual(data.size(), '\0');
+ ASSERT_THAT(RetryEINTR(read)(outf.get(), actual.data(), actual.size()),
+ SyscallSucceedsWithValue(actual.size()));
+ ASSERT_EQ(memcmp(data.data(), actual.data(), data.size()), 0);
+}
+
+} // namespace
+} // namespace testing
+} // namespace gvisor