mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
391 lines
13 KiB
C++
391 lines
13 KiB
C++
// Copyright 2020 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/fuse.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statfs.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "test/fuse/linux/fuse_base.h"
|
|
#include "test/util/fuse_util.h"
|
|
#include "test/util/test_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
class ReadTest : public FuseTest {
|
|
void SetUp() override {
|
|
FuseTest::SetUp();
|
|
test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_);
|
|
}
|
|
|
|
// TearDown overrides the parent's function
|
|
// to skip checking the unconsumed release request at the end.
|
|
void TearDown() override { UnmountFuse(); }
|
|
|
|
protected:
|
|
const std::string test_file_ = "test_file";
|
|
const mode_t test_file_mode_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
|
|
const uint64_t test_fh_ = 1;
|
|
const uint32_t open_flag_ = O_RDWR;
|
|
|
|
std::string test_file_path_;
|
|
|
|
PosixErrorOr<FileDescriptor> OpenTestFile(const std::string &path,
|
|
uint64_t size = 512) {
|
|
SetServerInodeLookup(test_file_, test_file_mode_, size);
|
|
|
|
struct fuse_out_header out_header_open = {
|
|
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
|
|
};
|
|
struct fuse_open_out out_payload_open = {
|
|
.fh = test_fh_,
|
|
.open_flags = open_flag_,
|
|
};
|
|
auto iov_out_open = FuseGenerateIovecs(out_header_open, out_payload_open);
|
|
SetServerResponse(FUSE_OPEN, iov_out_open);
|
|
|
|
auto res = Open(path.c_str(), open_flag_);
|
|
if (res.ok()) {
|
|
SkipServerActualRequest();
|
|
}
|
|
return res;
|
|
}
|
|
};
|
|
|
|
class ReadTestSmallMaxRead : public ReadTest {
|
|
void SetUp() override {
|
|
MountFuse(mountOpts);
|
|
SetUpFuseServer();
|
|
test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_);
|
|
}
|
|
|
|
protected:
|
|
constexpr static char mountOpts[] =
|
|
"rootmode=755,user_id=0,group_id=0,max_read=4096";
|
|
// 4096 is hard-coded as the max_read in mount options.
|
|
const int size_fragment = 4096;
|
|
};
|
|
|
|
TEST_F(ReadTest, ReadWhole) {
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
|
|
|
// Prepare for the read.
|
|
const int n_read = 5;
|
|
std::vector<char> data(n_read);
|
|
RandomizeBuffer(data.data(), data.size());
|
|
struct fuse_out_header out_header_read = {
|
|
.len =
|
|
static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read, data);
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
|
|
// Read the whole "file".
|
|
std::vector<char> buf(n_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), n_read),
|
|
SyscallSucceedsWithValue(n_read));
|
|
|
|
// Check the read request.
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
GetServerActualRequest(iov_in);
|
|
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, 0);
|
|
EXPECT_EQ(buf, data);
|
|
}
|
|
|
|
TEST_F(ReadTest, ReadPartial) {
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
|
|
|
// Prepare for the read.
|
|
const int n_data = 10;
|
|
std::vector<char> data(n_data);
|
|
RandomizeBuffer(data.data(), data.size());
|
|
// Note: due to read ahead, current read implementation will treat any
|
|
// response that is longer than requested as correct (i.e. not reach the EOF).
|
|
// Therefore, the test below should make sure the size to read does not exceed
|
|
// n_data.
|
|
struct fuse_out_header out_header_read = {
|
|
.len =
|
|
static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read, data);
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
|
|
std::vector<char> buf(n_data);
|
|
|
|
// Read 1 bytes.
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), 1), SyscallSucceedsWithValue(1));
|
|
|
|
// Check the 1-byte read request.
|
|
GetServerActualRequest(iov_in);
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, 0);
|
|
|
|
// Read 3 bytes.
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), 3), SyscallSucceedsWithValue(3));
|
|
|
|
// Check the 3-byte read request.
|
|
GetServerActualRequest(iov_in);
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_payload_read.offset, 1);
|
|
|
|
// Read 5 bytes.
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), 5), SyscallSucceedsWithValue(5));
|
|
|
|
// Check the 5-byte read request.
|
|
GetServerActualRequest(iov_in);
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_payload_read.offset, 4);
|
|
}
|
|
|
|
TEST_F(ReadTest, PRead) {
|
|
const int file_size = 512;
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, file_size));
|
|
|
|
// Prepare for the read.
|
|
const int n_read = 5;
|
|
std::vector<char> data(n_read);
|
|
RandomizeBuffer(data.data(), data.size());
|
|
struct fuse_out_header out_header_read = {
|
|
.len =
|
|
static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read, data);
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
|
|
// Read some bytes.
|
|
std::vector<char> buf(n_read);
|
|
const int offset_read = file_size >> 1;
|
|
EXPECT_THAT(pread(fd.get(), buf.data(), n_read, offset_read),
|
|
SyscallSucceedsWithValue(n_read));
|
|
|
|
// Check the read request.
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
GetServerActualRequest(iov_in);
|
|
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, offset_read);
|
|
EXPECT_EQ(buf, data);
|
|
}
|
|
|
|
TEST_F(ReadTest, ReadZero) {
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
|
|
|
// Issue the read.
|
|
std::vector<char> buf;
|
|
EXPECT_THAT(read(fd.get(), buf.data(), 0), SyscallSucceedsWithValue(0));
|
|
}
|
|
|
|
TEST_F(ReadTest, ReadShort) {
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
|
|
|
// Prepare for the short read.
|
|
const int n_read = 5;
|
|
std::vector<char> data(n_read >> 1);
|
|
RandomizeBuffer(data.data(), data.size());
|
|
struct fuse_out_header out_header_read = {
|
|
.len =
|
|
static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read, data);
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
|
|
// Read the whole "file".
|
|
std::vector<char> buf(n_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), n_read),
|
|
SyscallSucceedsWithValue(data.size()));
|
|
|
|
// Check the read request.
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
GetServerActualRequest(iov_in);
|
|
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, 0);
|
|
std::vector<char> short_buf(buf.begin(), buf.begin() + data.size());
|
|
EXPECT_EQ(short_buf, data);
|
|
}
|
|
|
|
TEST_F(ReadTest, ReadShortEOF) {
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
|
|
|
// Prepare for the short read.
|
|
struct fuse_out_header out_header_read = {
|
|
.len = static_cast<uint32_t>(sizeof(struct fuse_out_header)),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read);
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
|
|
// Read the whole "file".
|
|
const int n_read = 10;
|
|
std::vector<char> buf(n_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), n_read), SyscallSucceedsWithValue(0));
|
|
|
|
// Check the read request.
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
GetServerActualRequest(iov_in);
|
|
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, 0);
|
|
}
|
|
|
|
TEST_F(ReadTestSmallMaxRead, ReadSmallMaxRead) {
|
|
const int n_fragment = 10;
|
|
const int n_read = size_fragment * n_fragment;
|
|
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_read));
|
|
|
|
// Prepare for the read.
|
|
std::vector<char> data(size_fragment);
|
|
RandomizeBuffer(data.data(), data.size());
|
|
struct fuse_out_header out_header_read = {
|
|
.len =
|
|
static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read, data);
|
|
|
|
for (int i = 0; i < n_fragment; ++i) {
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
}
|
|
|
|
// Read the whole "file".
|
|
std::vector<char> buf(n_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), n_read),
|
|
SyscallSucceedsWithValue(n_read));
|
|
|
|
ASSERT_EQ(GetServerNumUnsentResponses(), 0);
|
|
ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment);
|
|
|
|
// Check each read segment.
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
|
|
for (int i = 0; i < n_fragment; ++i) {
|
|
GetServerActualRequest(iov_in);
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, i * size_fragment);
|
|
EXPECT_EQ(in_payload_read.size, size_fragment);
|
|
|
|
auto it = buf.begin() + i * size_fragment;
|
|
EXPECT_EQ(std::vector<char>(it, it + size_fragment), data);
|
|
}
|
|
}
|
|
|
|
TEST_F(ReadTestSmallMaxRead, ReadSmallMaxReadShort) {
|
|
const int n_fragment = 10;
|
|
const int n_read = size_fragment * n_fragment;
|
|
|
|
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_read));
|
|
|
|
// Prepare for the read.
|
|
std::vector<char> data(size_fragment);
|
|
RandomizeBuffer(data.data(), data.size());
|
|
struct fuse_out_header out_header_read = {
|
|
.len =
|
|
static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()),
|
|
};
|
|
auto iov_out_read = FuseGenerateIovecs(out_header_read, data);
|
|
|
|
for (int i = 0; i < n_fragment - 1; ++i) {
|
|
SetServerResponse(FUSE_READ, iov_out_read);
|
|
}
|
|
|
|
// The last fragment is a short read.
|
|
std::vector<char> half_data(data.begin(), data.begin() + (data.size() >> 1));
|
|
struct fuse_out_header out_header_read_short = {
|
|
.len = static_cast<uint32_t>(sizeof(struct fuse_out_header) +
|
|
half_data.size()),
|
|
};
|
|
auto iov_out_read_short =
|
|
FuseGenerateIovecs(out_header_read_short, half_data);
|
|
SetServerResponse(FUSE_READ, iov_out_read_short);
|
|
|
|
// Read the whole "file".
|
|
std::vector<char> buf(n_read);
|
|
EXPECT_THAT(read(fd.get(), buf.data(), n_read),
|
|
SyscallSucceedsWithValue(n_read - (data.size() >> 1)));
|
|
|
|
ASSERT_EQ(GetServerNumUnsentResponses(), 0);
|
|
ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment);
|
|
|
|
// Check each read segment.
|
|
struct fuse_in_header in_header_read;
|
|
struct fuse_read_in in_payload_read;
|
|
auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read);
|
|
|
|
for (int i = 0; i < n_fragment; ++i) {
|
|
GetServerActualRequest(iov_in);
|
|
EXPECT_EQ(in_payload_read.fh, test_fh_);
|
|
EXPECT_EQ(in_header_read.len,
|
|
sizeof(in_header_read) + sizeof(in_payload_read));
|
|
EXPECT_EQ(in_header_read.opcode, FUSE_READ);
|
|
EXPECT_EQ(in_payload_read.offset, i * size_fragment);
|
|
EXPECT_EQ(in_payload_read.size, size_fragment);
|
|
|
|
auto it = buf.begin() + i * size_fragment;
|
|
if (i != n_fragment - 1) {
|
|
EXPECT_EQ(std::vector<char>(it, it + data.size()), data);
|
|
} else {
|
|
EXPECT_EQ(std::vector<char>(it, it + half_data.size()), half_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|