summaryrefslogtreecommitdiffhomepage
path: root/test/fuse
diff options
context:
space:
mode:
authorJinmou Li <jinmli@google.com>2020-07-15 18:32:51 +0000
committerAndrei Vagin <avagin@gmail.com>2020-09-16 12:19:30 -0700
commit713400d6b0f2eef6a368bc9cb76a15ee3334d1e5 (patch)
treed3797a92704fd3a0d9ac768569d25bcf154c254f /test/fuse
parent4d26c9929de31cdfe3551d4b8be90a07f98fed55 (diff)
Implement FUSE_READ
Fixes #3206
Diffstat (limited to 'test/fuse')
-rw-r--r--test/fuse/BUILD5
-rw-r--r--test/fuse/linux/BUILD13
-rw-r--r--test/fuse/linux/fuse_base.cc17
-rw-r--r--test/fuse/linux/fuse_base.h13
-rw-r--r--test/fuse/linux/read_test.cc390
5 files changed, 429 insertions, 9 deletions
diff --git a/test/fuse/BUILD b/test/fuse/BUILD
index 8bde81e3c..cae51ce49 100644
--- a/test/fuse/BUILD
+++ b/test/fuse/BUILD
@@ -36,3 +36,8 @@ syscall_test(
fuse = "True",
test = "//test/fuse/linux:mkdir_test",
)
+
+syscall_test(
+ fuse = "True",
+ test = "//test/fuse/linux:read_test",
+)
diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD
index 298ea11f8..8afd28f16 100644
--- a/test/fuse/linux/BUILD
+++ b/test/fuse/linux/BUILD
@@ -112,3 +112,16 @@ cc_library(
"@com_google_absl//absl/strings:str_format",
],
)
+
+cc_binary(
+ name = "read_test",
+ testonly = 1,
+ srcs = ["read_test.cc"],
+ deps = [
+ gtest,
+ ":fuse_base",
+ "//test/util:fuse_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+) \ No newline at end of file
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
index 98b4e1466..e3c6b585c 100644
--- a/test/fuse/linux/fuse_base.cc
+++ b/test/fuse/linux/fuse_base.cc
@@ -129,7 +129,8 @@ void FuseTest::SkipServerActualRequest() {
// Sends the `kSetInodeLookup` command, expected mode, and the path of the
// inode to create under the mount point.
-void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode) {
+void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode,
+ uint64_t size) {
uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSetInodeLookup);
EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
SyscallSucceedsWithValue(sizeof(cmd)));
@@ -137,6 +138,9 @@ void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode) {
EXPECT_THAT(RetryEINTR(write)(sock_[0], &mode, sizeof(mode)),
SyscallSucceedsWithValue(sizeof(mode)));
+ EXPECT_THAT(RetryEINTR(write)(sock_[0], &size, sizeof(size)),
+ SyscallSucceedsWithValue(sizeof(size)));
+
// Pad 1 byte for null-terminate c-string.
EXPECT_THAT(RetryEINTR(write)(sock_[0], path.c_str(), path.size() + 1),
SyscallSucceedsWithValue(path.size() + 1));
@@ -144,10 +148,10 @@ void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode) {
WaitServerComplete();
}
-void FuseTest::MountFuse() {
+void FuseTest::MountFuse(const char* mountOpts) {
EXPECT_THAT(dev_fd_ = open("/dev/fuse", O_RDWR), SyscallSucceeds());
- std::string mount_opts = absl::StrFormat("fd=%d,%s", dev_fd_, kMountOpts);
+ std::string mount_opts = absl::StrFormat("fd=%d,%s", dev_fd_, mountOpts);
mount_point_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
EXPECT_THAT(mount("fuse", mount_point_.path().c_str(), "fuse",
MS_NODEV | MS_NOSUID, mount_opts.c_str()),
@@ -311,11 +315,15 @@ void FuseTest::ServerHandleCommand() {
// request with this specific path comes in.
void FuseTest::ServerReceiveInodeLookup() {
mode_t mode;
+ uint64_t size;
std::vector<char> buf(FUSE_MIN_READ_BUFFER);
EXPECT_THAT(RetryEINTR(read)(sock_[1], &mode, sizeof(mode)),
SyscallSucceedsWithValue(sizeof(mode)));
+ EXPECT_THAT(RetryEINTR(read)(sock_[1], &size, sizeof(size)),
+ SyscallSucceedsWithValue(sizeof(size)));
+
EXPECT_THAT(RetryEINTR(read)(sock_[1], buf.data(), buf.size()),
SyscallSucceeds());
@@ -332,6 +340,9 @@ void FuseTest::ServerReceiveInodeLookup() {
// comply with the unqiueness of different path.
++nodeid_;
+ // Set the size.
+ out_payload.attr.size = size;
+
memcpy(buf.data(), &out_header, sizeof(out_header));
memcpy(buf.data() + sizeof(out_header), &out_payload, sizeof(out_payload));
lookups_.AddMemBlock(FUSE_LOOKUP, buf.data(), out_len);
diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h
index ff4c4499d..452748d6d 100644
--- a/test/fuse/linux/fuse_base.h
+++ b/test/fuse/linux/fuse_base.h
@@ -137,7 +137,8 @@ class FuseTest : public ::testing::Test {
// path, pretending there is an inode and avoid ENOENT when testing. If mode
// is not given, it creates a regular file with mode 0600.
void SetServerInodeLookup(const std::string& path,
- mode_t mode = S_IFREG | S_IRUSR | S_IWUSR);
+ mode_t mode = S_IFREG | S_IRUSR | S_IWUSR,
+ uint64_t size = 512);
// Called by the testing thread to ask the FUSE server for its next received
// FUSE request. Be sure to use the corresponding struct of iovec to receive
@@ -166,16 +167,16 @@ class FuseTest : public ::testing::Test {
protected:
TempPath mount_point_;
- // Unmounts the mountpoint of the FUSE server.
- void UnmountFuse();
-
- private:
// Opens /dev/fuse and inherit the file descriptor for the FUSE server.
- void MountFuse();
+ void MountFuse(const char* mountOpts = kMountOpts);
// Creates a socketpair for communication and forks FUSE server.
void SetUpFuseServer();
+ // Unmounts the mountpoint of the FUSE server.
+ void UnmountFuse();
+
+ private:
// Sends a FuseTestCmd and gets a uint32_t data from the FUSE server.
inline uint32_t GetServerData(uint32_t cmd);
diff --git a/test/fuse/linux/read_test.cc b/test/fuse/linux/read_test.cc
new file mode 100644
index 000000000..c702651bd
--- /dev/null
+++ b/test/fuse/linux/read_test.cc
@@ -0,0 +1,390 @@
+// 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 \ No newline at end of file