summaryrefslogtreecommitdiffhomepage
path: root/test/fuse/linux/fuse_base.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/fuse/linux/fuse_base.cc')
-rw-r--r--test/fuse/linux/fuse_base.cc447
1 files changed, 0 insertions, 447 deletions
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
deleted file mode 100644
index 5b45804e1..000000000
--- a/test/fuse/linux/fuse_base.cc
+++ /dev/null
@@ -1,447 +0,0 @@
-// 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 "test/fuse/linux/fuse_base.h"
-
-#include <fcntl.h>
-#include <linux/fuse.h>
-#include <poll.h>
-#include <sys/mount.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_format.h"
-#include "test/util/fuse_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-void FuseTest::SetUp() {
- MountFuse();
- SetUpFuseServer();
-}
-
-void FuseTest::TearDown() {
- EXPECT_EQ(GetServerNumUnconsumedRequests(), 0);
- EXPECT_EQ(GetServerNumUnsentResponses(), 0);
- UnmountFuse();
-}
-
-// Sends 3 parts of data to the FUSE server:
-// 1. The `kSetResponse` command
-// 2. The expected opcode
-// 3. The fake FUSE response
-// Then waits for the FUSE server to notify its completion.
-void FuseTest::SetServerResponse(uint32_t opcode,
- std::vector<struct iovec>& iovecs) {
- uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSetResponse);
- EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
- SyscallSucceedsWithValue(sizeof(cmd)));
-
- EXPECT_THAT(RetryEINTR(write)(sock_[0], &opcode, sizeof(opcode)),
- SyscallSucceedsWithValue(sizeof(opcode)));
-
- EXPECT_THAT(RetryEINTR(writev)(sock_[0], iovecs.data(), iovecs.size()),
- SyscallSucceeds());
-
- WaitServerComplete();
-}
-
-// Waits for the FUSE server to finish its blocking job and check if it
-// completes without errors.
-void FuseTest::WaitServerComplete() {
- uint32_t success;
- EXPECT_THAT(RetryEINTR(read)(sock_[0], &success, sizeof(success)),
- SyscallSucceedsWithValue(sizeof(success)));
- ASSERT_EQ(success, 1);
-}
-
-// Sends the `kGetRequest` command to the FUSE server, then reads the next
-// request into iovec struct. The order of calling this function should be
-// the same as the one of SetServerResponse().
-void FuseTest::GetServerActualRequest(std::vector<struct iovec>& iovecs) {
- uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kGetRequest);
- EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
- SyscallSucceedsWithValue(sizeof(cmd)));
-
- EXPECT_THAT(RetryEINTR(readv)(sock_[0], iovecs.data(), iovecs.size()),
- SyscallSucceeds());
-
- WaitServerComplete();
-}
-
-// Sends a FuseTestCmd command to the FUSE server, reads from the socket, and
-// returns the corresponding data.
-uint32_t FuseTest::GetServerData(uint32_t cmd) {
- uint32_t data;
- EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
- SyscallSucceedsWithValue(sizeof(cmd)));
-
- EXPECT_THAT(RetryEINTR(read)(sock_[0], &data, sizeof(data)),
- SyscallSucceedsWithValue(sizeof(data)));
-
- WaitServerComplete();
- return data;
-}
-
-uint32_t FuseTest::GetServerNumUnconsumedRequests() {
- return GetServerData(
- static_cast<uint32_t>(FuseTestCmd::kGetNumUnconsumedRequests));
-}
-
-uint32_t FuseTest::GetServerNumUnsentResponses() {
- return GetServerData(
- static_cast<uint32_t>(FuseTestCmd::kGetNumUnsentResponses));
-}
-
-uint32_t FuseTest::GetServerTotalReceivedBytes() {
- return GetServerData(
- static_cast<uint32_t>(FuseTestCmd::kGetTotalReceivedBytes));
-}
-
-// Sends the `kSkipRequest` command to the FUSE server, which would skip
-// current stored request data.
-void FuseTest::SkipServerActualRequest() {
- uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSkipRequest);
- EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
- SyscallSucceedsWithValue(sizeof(cmd)));
-
- WaitServerComplete();
-}
-
-// 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,
- uint64_t size) {
- uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSetInodeLookup);
- EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
- SyscallSucceedsWithValue(sizeof(cmd)));
-
- 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));
-
- WaitServerComplete();
-}
-
-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_, 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()),
- SyscallSucceeds());
-}
-
-void FuseTest::UnmountFuse() {
- EXPECT_THAT(umount(mount_point_.path().c_str()), SyscallSucceeds());
- // TODO(gvisor.dev/issue/3330): ensure the process is terminated successfully.
-}
-
-// Consumes the first FUSE request and returns the corresponding PosixError.
-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()));
-
- struct fuse_out_header out_header = {
- .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out),
- .error = 0,
- .unique = 2,
- };
- // Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED
- // error in the initialization of FUSE connection.
- 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()));
- return NoError();
-}
-
-// Reads 1 expected opcode and a fake response from socket and save them into
-// the serial buffer of this testing instance.
-void FuseTest::ServerReceiveResponse() {
- ssize_t len;
- uint32_t opcode;
- std::vector<char> buf(FUSE_MIN_READ_BUFFER);
- EXPECT_THAT(RetryEINTR(read)(sock_[1], &opcode, sizeof(opcode)),
- SyscallSucceedsWithValue(sizeof(opcode)));
-
- EXPECT_THAT(len = RetryEINTR(read)(sock_[1], buf.data(), buf.size()),
- SyscallSucceeds());
-
- responses_.AddMemBlock(opcode, buf.data(), len);
-}
-
-// Writes 1 byte of success indicator through socket.
-void FuseTest::ServerCompleteWith(bool success) {
- uint32_t data = success ? 1 : 0;
- ServerSendData(data);
-}
-
-// ServerFuseLoop is the implementation of the fake FUSE server. Monitors 2
-// file descriptors: /dev/fuse and sock_[1]. Events from /dev/fuse are FUSE
-// requests and events from sock_[1] are FUSE testing commands, leading by
-// a FuseTestCmd data to indicate the command.
-void FuseTest::ServerFuseLoop() {
- const int nfds = 2;
- struct pollfd fds[nfds] = {
- {
- .fd = dev_fd_,
- .events = POLL_IN | POLLHUP | POLLERR | POLLNVAL,
- },
- {
- .fd = sock_[1],
- .events = POLL_IN | POLLHUP | POLLERR | POLLNVAL,
- },
- };
-
- while (true) {
- ASSERT_THAT(poll(fds, nfds, -1), SyscallSucceeds());
-
- for (int fd_idx = 0; fd_idx < nfds; ++fd_idx) {
- if (fds[fd_idx].revents == 0) continue;
-
- ASSERT_EQ(fds[fd_idx].revents, POLL_IN);
- if (fds[fd_idx].fd == sock_[1]) {
- ServerHandleCommand();
- } else if (fds[fd_idx].fd == dev_fd_) {
- ServerProcessFuseRequest();
- }
- }
- }
-}
-
-// SetUpFuseServer creates 1 socketpair and fork the process. The parent thread
-// 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(const struct fuse_init_out* payload) {
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_), SyscallSucceeds());
-
- switch (fork()) {
- case -1:
- GTEST_FAIL();
- return;
- case 0:
- break;
- default:
- ASSERT_THAT(close(sock_[1]), SyscallSucceeds());
- WaitServerComplete();
- return;
- }
-
- // Begin child thread, i.e. the FUSE server.
- ASSERT_THAT(close(sock_[0]), SyscallSucceeds());
- ServerCompleteWith(ServerConsumeFuseInit(payload).ok());
- ServerFuseLoop();
- _exit(0);
-}
-
-void FuseTest::ServerSendData(uint32_t data) {
- EXPECT_THAT(RetryEINTR(write)(sock_[1], &data, sizeof(data)),
- SyscallSucceedsWithValue(sizeof(data)));
-}
-
-// Reads FuseTestCmd sent from testing thread and routes to correct handler.
-// Since each command should be a blocking operation, a `ServerCompleteWith()`
-// is required after the switch keyword.
-void FuseTest::ServerHandleCommand() {
- uint32_t cmd;
- EXPECT_THAT(RetryEINTR(read)(sock_[1], &cmd, sizeof(cmd)),
- SyscallSucceedsWithValue(sizeof(cmd)));
-
- switch (static_cast<FuseTestCmd>(cmd)) {
- case FuseTestCmd::kSetResponse:
- ServerReceiveResponse();
- break;
- case FuseTestCmd::kSetInodeLookup:
- ServerReceiveInodeLookup();
- break;
- case FuseTestCmd::kGetRequest:
- ServerSendReceivedRequest();
- break;
- case FuseTestCmd::kGetTotalReceivedBytes:
- ServerSendData(static_cast<uint32_t>(requests_.UsedBytes()));
- break;
- case FuseTestCmd::kGetNumUnconsumedRequests:
- ServerSendData(static_cast<uint32_t>(requests_.RemainingBlocks()));
- break;
- case FuseTestCmd::kGetNumUnsentResponses:
- ServerSendData(static_cast<uint32_t>(responses_.RemainingBlocks()));
- break;
- case FuseTestCmd::kSkipRequest:
- ServerSkipReceivedRequest();
- break;
- default:
- FAIL() << "Unknown FuseTestCmd " << cmd;
- break;
- }
-
- ServerCompleteWith(!HasFailure());
-}
-
-// Reads the expected file mode and the path of one file. Crafts a basic
-// `fuse_entry_out` memory block and inserts into a map for future use.
-// The FUSE server will always return this response if a FUSE_LOOKUP
-// 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());
-
- std::string path(buf.data());
-
- uint32_t out_len =
- sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out);
- struct fuse_out_header out_header = {
- .len = out_len,
- .error = 0,
- };
- struct fuse_entry_out out_payload = DefaultEntryOut(mode, nodeid_);
- // Since this is only used in test, nodeid_ is simply increased by 1 to
- // 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);
- lookup_map_[path] = lookups_.Next();
-}
-
-// Sends the received request pointed by current cursor and advances cursor.
-void FuseTest::ServerSendReceivedRequest() {
- if (requests_.End()) {
- FAIL() << "No more received request.";
- return;
- }
- auto mem_block = requests_.Next();
- EXPECT_THAT(
- RetryEINTR(write)(sock_[1], requests_.DataAtOffset(mem_block.offset),
- mem_block.len),
- SyscallSucceedsWithValue(mem_block.len));
-}
-
-// Skip the request pointed by current cursor.
-void FuseTest::ServerSkipReceivedRequest() {
- if (requests_.End()) {
- FAIL() << "No more received request.";
- return;
- }
- requests_.Next();
-}
-
-// Handles FUSE request. Reads request from /dev/fuse, checks if it has the
-// same opcode as expected, and responds with the saved fake FUSE response.
-// The FUSE request is copied to the serial buffer and can be retrieved one-
-// by-one by calling GetServerActualRequest from testing thread.
-void FuseTest::ServerProcessFuseRequest() {
- ssize_t len;
- std::vector<char> buf(FUSE_MIN_READ_BUFFER);
-
- // Read FUSE request.
- EXPECT_THAT(len = RetryEINTR(read)(dev_fd_, buf.data(), buf.size()),
- SyscallSucceeds());
- fuse_in_header* in_header = reinterpret_cast<fuse_in_header*>(buf.data());
-
- // Check if this is a preset FUSE_LOOKUP path.
- if (in_header->opcode == FUSE_LOOKUP) {
- std::string path(buf.data() + sizeof(struct fuse_in_header));
- auto it = lookup_map_.find(path);
- if (it != lookup_map_.end()) {
- // Matches a preset path. Reply with fake data and skip saving the
- // request.
- ServerRespondFuseSuccess(lookups_, it->second, in_header->unique);
- return;
- }
- }
-
- requests_.AddMemBlock(in_header->opcode, buf.data(), len);
-
- if (in_header->opcode == FUSE_RELEASE || in_header->opcode == FUSE_RELEASEDIR)
- return;
- // Check if there is a corresponding response.
- if (responses_.End()) {
- GTEST_NONFATAL_FAILURE_("No more FUSE response is expected");
- ServerRespondFuseError(in_header->unique);
- return;
- }
- auto mem_block = responses_.Next();
- if (in_header->opcode != mem_block.opcode) {
- std::string message = absl::StrFormat("Expect opcode %d but got %d",
- mem_block.opcode, in_header->opcode);
- GTEST_NONFATAL_FAILURE_(message.c_str());
- // We won't get correct response if opcode is not expected. Send error
- // response here to avoid wrong parsing by VFS.
- ServerRespondFuseError(in_header->unique);
- return;
- }
-
- // Write FUSE response.
- ServerRespondFuseSuccess(responses_, mem_block, in_header->unique);
-}
-
-void FuseTest::ServerRespondFuseSuccess(FuseMemBuffer& mem_buf,
- const FuseMemBlock& block,
- uint64_t unique) {
- fuse_out_header* out_header =
- reinterpret_cast<fuse_out_header*>(mem_buf.DataAtOffset(block.offset));
-
- // Patch `unique` in fuse_out_header to avoid EINVAL caused by responding
- // with an unknown `unique`.
- out_header->unique = unique;
- EXPECT_THAT(RetryEINTR(write)(dev_fd_, out_header, block.len),
- SyscallSucceedsWithValue(block.len));
-}
-
-void FuseTest::ServerRespondFuseError(uint64_t unique) {
- fuse_out_header out_header = {
- .len = sizeof(struct fuse_out_header),
- .error = ENOSYS,
- .unique = unique,
- };
- EXPECT_THAT(RetryEINTR(write)(dev_fd_, &out_header, sizeof(out_header)),
- SyscallSucceedsWithValue(sizeof(out_header)));
-}
-
-} // namespace testing
-} // namespace gvisor