157 lines
5.2 KiB
C++
157 lines
5.2 KiB
C++
// 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
|