summaryrefslogtreecommitdiffhomepage
path: root/test/fuse/linux
diff options
context:
space:
mode:
authorJinmou Li <jinmli@google.com>2020-09-01 01:49:57 +0000
committerAndrei Vagin <avagin@gmail.com>2020-09-16 12:19:30 -0700
commit98faed55e682cf34bb713c37b063a7d1da5e8352 (patch)
tree1301aecbd534cb4eac712cf65e808914d3157559 /test/fuse/linux
parent18f1e1c91b05059c333197a2a6198716c12508e7 (diff)
Implement FUSE_WRITE
This commit adds basic write(2) support for FUSE.
Diffstat (limited to 'test/fuse/linux')
-rw-r--r--test/fuse/linux/BUILD13
-rw-r--r--test/fuse/linux/fuse_base.cc13
-rw-r--r--test/fuse/linux/fuse_base.h7
-rw-r--r--test/fuse/linux/write_test.cc303
4 files changed, 327 insertions, 9 deletions
diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD
index 2bb956af9..cc5bd560e 100644
--- a/test/fuse/linux/BUILD
+++ b/test/fuse/linux/BUILD
@@ -155,6 +155,19 @@ cc_binary(
)
cc_binary(
+ name = "write_test",
+ testonly = 1,
+ srcs = ["write_test.cc"],
+ deps = [
+ gtest,
+ ":fuse_base",
+ "//test/util:fuse_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "create_test",
testonly = 1,
srcs = ["create_test.cc"],
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
index e3c6b585c..a033db117 100644
--- a/test/fuse/linux/fuse_base.cc
+++ b/test/fuse/linux/fuse_base.cc
@@ -164,7 +164,8 @@ void FuseTest::UnmountFuse() {
}
// Consumes the first FUSE request and returns the corresponding PosixError.
-PosixError FuseTest::ServerConsumeFuseInit() {
+PosixError FuseTest::ServerConsumeFuseInit(
+ const struct fuse_init_out* out_payload) {
std::vector<char> buf(FUSE_MIN_READ_BUFFER);
RETURN_ERROR_IF_SYSCALL_FAIL(
RetryEINTR(read)(dev_fd_, buf.data(), buf.size()));
@@ -176,10 +177,8 @@ PosixError FuseTest::ServerConsumeFuseInit() {
};
// Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED
// error in the initialization of FUSE connection.
- struct fuse_init_out out_payload = {
- .major = 7,
- };
- auto iov_out = FuseGenerateIovecs(out_header, out_payload);
+ auto iov_out = FuseGenerateIovecs(
+ out_header, *const_cast<struct fuse_init_out*>(out_payload));
RETURN_ERROR_IF_SYSCALL_FAIL(
RetryEINTR(writev)(dev_fd_, iov_out.data(), iov_out.size()));
@@ -244,7 +243,7 @@ void FuseTest::ServerFuseLoop() {
// becomes testing thread and the child thread becomes the FUSE server running
// in background. These 2 threads are connected via socketpair. sock_[0] is
// opened in testing thread and sock_[1] is opened in the FUSE server.
-void FuseTest::SetUpFuseServer() {
+void FuseTest::SetUpFuseServer(const struct fuse_init_out* payload) {
ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_), SyscallSucceeds());
switch (fork()) {
@@ -261,7 +260,7 @@ void FuseTest::SetUpFuseServer() {
// Begin child thread, i.e. the FUSE server.
ASSERT_THAT(close(sock_[0]), SyscallSucceeds());
- ServerCompleteWith(ServerConsumeFuseInit().ok());
+ ServerCompleteWith(ServerConsumeFuseInit(payload).ok());
ServerFuseLoop();
_exit(0);
}
diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h
index 452748d6d..6ad296ca2 100644
--- a/test/fuse/linux/fuse_base.h
+++ b/test/fuse/linux/fuse_base.h
@@ -33,6 +33,8 @@ namespace testing {
constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0";
+constexpr struct fuse_init_out kDefaultFUSEInitOutPayload = {.major = 7};
+
// Internal commands used to communicate between testing thread and the FUSE
// server. See test/fuse/README.md for further detail.
enum class FuseTestCmd {
@@ -171,7 +173,8 @@ class FuseTest : public ::testing::Test {
void MountFuse(const char* mountOpts = kMountOpts);
// Creates a socketpair for communication and forks FUSE server.
- void SetUpFuseServer();
+ void SetUpFuseServer(
+ const struct fuse_init_out* payload = &kDefaultFUSEInitOutPayload);
// Unmounts the mountpoint of the FUSE server.
void UnmountFuse();
@@ -194,7 +197,7 @@ class FuseTest : public ::testing::Test {
// Consumes the first FUSE request when mounting FUSE. Replies with a
// response with empty payload.
- PosixError ServerConsumeFuseInit();
+ PosixError ServerConsumeFuseInit(const struct fuse_init_out* payload);
// A command switch that dispatch different FuseTestCmd to its handler.
void ServerHandleCommand();
diff --git a/test/fuse/linux/write_test.cc b/test/fuse/linux/write_test.cc
new file mode 100644
index 000000000..e7a1aff13
--- /dev/null
+++ b/test/fuse/linux/write_test.cc
@@ -0,0 +1,303 @@
+// 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 WriteTest : 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 WriteTestSmallMaxWrite : public WriteTest {
+ void SetUp() override {
+ MountFuse();
+ SetUpFuseServer(&fuse_init_payload);
+ test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_);
+ }
+
+ protected:
+ const static uint32_t max_write_ = 4096;
+ constexpr static struct fuse_init_out fuse_init_payload = {
+ .major = 7,
+ .max_write = max_write_,
+ };
+
+ const uint32_t size_fragment = max_write_;
+};
+
+TEST_F(WriteTest, WriteNormal) {
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
+
+ // Prepare for the write.
+ const int n_write = 10;
+ struct fuse_out_header out_header_write = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
+ };
+ struct fuse_write_out out_payload_write = {
+ .size = n_write,
+ };
+ auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
+ SetServerResponse(FUSE_WRITE, iov_out_write);
+
+ // Issue the write.
+ std::vector<char> buf(n_write);
+ RandomizeBuffer(buf.data(), buf.size());
+ EXPECT_THAT(write(fd.get(), buf.data(), n_write),
+ SyscallSucceedsWithValue(n_write));
+
+ // Check the write request.
+ struct fuse_in_header in_header_write;
+ struct fuse_write_in in_payload_write;
+ std::vector<char> payload_buf(n_write);
+ auto iov_in_write =
+ FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
+ GetServerActualRequest(iov_in_write);
+
+ EXPECT_EQ(in_payload_write.fh, test_fh_);
+ EXPECT_EQ(in_header_write.len,
+ sizeof(in_header_write) + sizeof(in_payload_write));
+ EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
+ EXPECT_EQ(in_payload_write.offset, 0);
+ EXPECT_EQ(in_payload_write.size, n_write);
+ EXPECT_EQ(buf, payload_buf);
+}
+
+TEST_F(WriteTest, WriteShort) {
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
+
+ // Prepare for the write.
+ const int n_write = 10, n_written = 5;
+ struct fuse_out_header out_header_write = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
+ };
+ struct fuse_write_out out_payload_write = {
+ .size = n_written,
+ };
+ auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
+ SetServerResponse(FUSE_WRITE, iov_out_write);
+
+ // Issue the write.
+ std::vector<char> buf(n_write);
+ RandomizeBuffer(buf.data(), buf.size());
+ EXPECT_THAT(write(fd.get(), buf.data(), n_write),
+ SyscallSucceedsWithValue(n_written));
+
+ // Check the write request.
+ struct fuse_in_header in_header_write;
+ struct fuse_write_in in_payload_write;
+ std::vector<char> payload_buf(n_write);
+ auto iov_in_write =
+ FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
+ GetServerActualRequest(iov_in_write);
+
+ EXPECT_EQ(in_payload_write.fh, test_fh_);
+ EXPECT_EQ(in_header_write.len,
+ sizeof(in_header_write) + sizeof(in_payload_write));
+ EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
+ EXPECT_EQ(in_payload_write.offset, 0);
+ EXPECT_EQ(in_payload_write.size, n_write);
+ EXPECT_EQ(buf, payload_buf);
+}
+
+TEST_F(WriteTest, WriteShortZero) {
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
+
+ // Prepare for the write.
+ const int n_write = 10;
+ struct fuse_out_header out_header_write = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
+ };
+ struct fuse_write_out out_payload_write = {
+ .size = 0,
+ };
+ auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
+ SetServerResponse(FUSE_WRITE, iov_out_write);
+
+ // Issue the write.
+ std::vector<char> buf(n_write);
+ RandomizeBuffer(buf.data(), buf.size());
+ EXPECT_THAT(write(fd.get(), buf.data(), n_write), SyscallFailsWithErrno(EIO));
+
+ // Check the write request.
+ struct fuse_in_header in_header_write;
+ struct fuse_write_in in_payload_write;
+ std::vector<char> payload_buf(n_write);
+ auto iov_in_write =
+ FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
+ GetServerActualRequest(iov_in_write);
+
+ EXPECT_EQ(in_payload_write.fh, test_fh_);
+ EXPECT_EQ(in_header_write.len,
+ sizeof(in_header_write) + sizeof(in_payload_write));
+ EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
+ EXPECT_EQ(in_payload_write.offset, 0);
+ EXPECT_EQ(in_payload_write.size, n_write);
+ EXPECT_EQ(buf, payload_buf);
+}
+
+TEST_F(WriteTest, WriteZero) {
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
+
+ // Issue the write.
+ std::vector<char> buf(0);
+ EXPECT_THAT(write(fd.get(), buf.data(), 0), SyscallSucceedsWithValue(0));
+}
+
+TEST_F(WriteTest, PWrite) {
+ const int file_size = 512;
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, file_size));
+
+ // Prepare for the write.
+ const int n_write = 10;
+ struct fuse_out_header out_header_write = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
+ };
+ struct fuse_write_out out_payload_write = {
+ .size = n_write,
+ };
+ auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
+ SetServerResponse(FUSE_WRITE, iov_out_write);
+
+ // Issue the write.
+ std::vector<char> buf(n_write);
+ RandomizeBuffer(buf.data(), buf.size());
+ const int offset_write = file_size >> 1;
+ EXPECT_THAT(pwrite(fd.get(), buf.data(), n_write, offset_write),
+ SyscallSucceedsWithValue(n_write));
+
+ // Check the write request.
+ struct fuse_in_header in_header_write;
+ struct fuse_write_in in_payload_write;
+ std::vector<char> payload_buf(n_write);
+ auto iov_in_write =
+ FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
+ GetServerActualRequest(iov_in_write);
+
+ EXPECT_EQ(in_payload_write.fh, test_fh_);
+ EXPECT_EQ(in_header_write.len,
+ sizeof(in_header_write) + sizeof(in_payload_write));
+ EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
+ EXPECT_EQ(in_payload_write.offset, offset_write);
+ EXPECT_EQ(in_payload_write.size, n_write);
+ EXPECT_EQ(buf, payload_buf);
+}
+
+TEST_F(WriteTestSmallMaxWrite, WriteSmallMaxWrie) {
+ const int n_fragment = 10;
+ const int n_write = size_fragment * n_fragment;
+
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_write));
+
+ // Prepare for the write.
+ struct fuse_out_header out_header_write = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
+ };
+ struct fuse_write_out out_payload_write = {
+ .size = size_fragment,
+ };
+ auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
+
+ for (int i = 0; i < n_fragment; ++i) {
+ SetServerResponse(FUSE_WRITE, iov_out_write);
+ }
+
+ // Issue the write.
+ std::vector<char> buf(n_write);
+ RandomizeBuffer(buf.data(), buf.size());
+ EXPECT_THAT(write(fd.get(), buf.data(), n_write),
+ SyscallSucceedsWithValue(n_write));
+
+ ASSERT_EQ(GetServerNumUnsentResponses(), 0);
+ ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment);
+
+ // Check the write request.
+ struct fuse_in_header in_header_write;
+ struct fuse_write_in in_payload_write;
+ std::vector<char> payload_buf(size_fragment);
+ auto iov_in_write =
+ FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
+
+ for (int i = 0; i < n_fragment; ++i) {
+ GetServerActualRequest(iov_in_write);
+
+ EXPECT_EQ(in_payload_write.fh, test_fh_);
+ EXPECT_EQ(in_header_write.len,
+ sizeof(in_header_write) + sizeof(in_payload_write));
+ EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
+ EXPECT_EQ(in_payload_write.offset, i * size_fragment);
+ EXPECT_EQ(in_payload_write.size, size_fragment);
+
+ auto it = buf.begin() + i * size_fragment;
+ EXPECT_EQ(std::vector<char>(it, it + size_fragment), payload_buf);
+ }
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor \ No newline at end of file