diff options
Diffstat (limited to 'test/fuse/linux/fuse_base.cc')
-rw-r--r-- | test/fuse/linux/fuse_base.cc | 447 |
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 |