diff options
Diffstat (limited to 'test/fuse/linux/fuse_base.h')
-rw-r--r-- | test/fuse/linux/fuse_base.h | 172 |
1 files changed, 131 insertions, 41 deletions
diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h index 3a2f255a9..b610d0f54 100644 --- a/test/fuse/linux/fuse_base.h +++ b/test/fuse/linux/fuse_base.h @@ -16,8 +16,10 @@ #define GVISOR_TEST_FUSE_FUSE_BASE_H_ #include <linux/fuse.h> +#include <string.h> #include <sys/uio.h> +#include <iostream> #include <vector> #include "gtest/gtest.h" @@ -29,68 +31,156 @@ namespace testing { constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0"; -class FuseTest : public ::testing::Test { +// Internal commands used to communicate between testing thread and the FUSE +// server. See test/fuse/README.md for further detail. +enum class FuseTestCmd { + kSetResponse = 0, + kGetRequest, +}; + +// Holds the information of a memory block in a serial buffer. +struct FuseMemBlock { + uint32_t opcode; + size_t offset; + size_t len; +}; + +// A wrapper of a simple serial buffer that can be used with read(2) and +// write(2). Contains a cursor to indicate accessing. This class is not thread- +// safe and can only be used in single-thread version. +class FuseMemBuffer { public: - FuseTest() { - buf_.resize(FUSE_MIN_READ_BUFFER); - mem_in_.resize(FUSE_MIN_READ_BUFFER); - mem_out_.resize(FUSE_MIN_READ_BUFFER); + FuseMemBuffer() : cursor_(0) { + // To read from /dev/fuse, a buffer needs at least FUSE_MIN_READ_BUFFER + // bytes to avoid EINVAL. FuseMemBuffer holds memory that can accommodate + // a sequence of FUSE request/response, so it is initiated with double + // minimal requirement. + mem_.resize(FUSE_MIN_READ_BUFFER * 2); } + + // Returns whether there is no memory block. + bool Empty() { return blocks_.empty(); } + + // Returns if there is no more remaining memory blocks. + bool End() { return cursor_ == blocks_.size(); } + + // Returns how many bytes that have been received. + size_t UsedBytes() { + return Empty() ? 0 : blocks_.back().offset + blocks_.back().len; + } + + // Returns the available bytes remains in the serial buffer. + size_t AvailBytes() { return mem_.size() - UsedBytes(); } + + // Appends a memory block information that starts at the tail of the serial + // buffer. /dev/fuse requires at least FUSE_MIN_READ_BUFFER bytes to read, or + // it will issue EINVAL. If it is not enough, just double the buffer length. + void AddMemBlock(uint32_t opcode, void* data, size_t len) { + if (AvailBytes() < FUSE_MIN_READ_BUFFER) { + mem_.resize(mem_.size() << 1); + } + size_t offset = UsedBytes(); + memcpy(mem_.data() + offset, data, len); + blocks_.push_back(FuseMemBlock{opcode, offset, len}); + } + + // Returns the memory address at a specific offset. Used with read(2) or + // write(2). + char* DataAtOffset(size_t offset) { return mem_.data() + offset; } + + // Returns current memory block pointed by the cursor and increase by 1. + FuseMemBlock Next() { + if (End()) { + std::cerr << "Buffer is already exhausted." << std::endl; + return FuseMemBlock{}; + } + return blocks_[cursor_++]; + } + + // Returns the number of the blocks that has not been requested. + size_t RemainingBlocks() { return blocks_.size() - cursor_; } + + private: + size_t cursor_; + std::vector<FuseMemBlock> blocks_; + std::vector<char> mem_; +}; + +// FuseTest base class is useful in FUSE integration test. Inherit this class +// to automatically set up a fake FUSE server and use the member functions +// to manipulate with it. Refer to test/fuse/README.md for detailed explanation. +class FuseTest : public ::testing::Test { + public: void SetUp() override; void TearDown() override; - // CompareRequest is used by the FUSE server and should be implemented to - // compare different FUSE operations. It compares the actual FUSE input - // request with the expected one set by `SetExpected()`. - virtual bool CompareRequest(void* expected_mem, size_t expected_len, - void* real_mem, size_t real_len); - - // SetExpected is called by the testing main thread. Writes a request- - // response pair into FUSE server's member variables via pipe. - void SetExpected(struct iovec* iov_in, int iov_in_cnt, struct iovec* iov_out, - int iov_out_cnt); + // Called by the testing thread to set up a fake response for an expected + // opcode via socket. This can be used multiple times to define a sequence of + // expected FUSE reactions. + void SetServerResponse(uint32_t opcode, std::vector<struct iovec>& iovecs); - // WaitCompleted waits for FUSE server to complete its processing. It - // complains if the FUSE server responds failure during tests. - void WaitCompleted(); + // 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 + // data from server. + void GetServerActualRequest(std::vector<struct iovec>& iovecs); protected: TempPath mount_point_; private: + // Opens /dev/fuse and inherit the file descriptor for the FUSE server. void MountFuse(); + + // Unmounts the mountpoint of the FUSE server. void UnmountFuse(); - // ConsumeFuseInit is only used during FUSE server setup. - PosixError ConsumeFuseInit(); + // Creates a socketpair for communication and forks FUSE server. + void SetUpFuseServer(); - // ReceiveExpected is the FUSE server side's corresponding code of - // `SetExpected()`. Save the request-response pair into its memory. - void ReceiveExpected(); + // Waits for FUSE server to complete its processing. Complains if the FUSE + // server responds any failure during tests. + void WaitServerComplete(); - // MarkDone is used by the FUSE server to tell testing main if it's OK to - // proceed next command. - void MarkDone(bool success); + // The FUSE server stays here and waits next command or FUSE request until it + // is terminated. + void ServerFuseLoop(); - // FuseLoop is where the FUSE server stay until it is terminated. - void FuseLoop(); + // Used by the FUSE server to tell testing thread if it is OK to proceed next + // command. Will be issued after processing each FuseTestCmd. + void ServerCompleteWith(bool success); - // SetUpFuseServer creates 2 pipes for communication and forks FUSE server. - void SetUpFuseServer(); + // Consumes the first FUSE request when mounting FUSE. Replies with a + // response with empty payload. + PosixError ServerConsumeFuseInit(); + + // A command switch that dispatch different FuseTestCmd to its handler. + void ServerHandleCommand(); + + // The FUSE server side's corresponding code of `SetServerResponse()`. + // Handles `kSetResponse` command. Saves the fake response into its output + // memory queue. + void ServerReceiveResponse(); + + // The FUSE server side's corresponding code of `GetServerActualRequest()`. + // Handles `kGetRequest` command. Sends the next received request pointed by + // the cursor. + void ServerSendReceivedRequest(); - // GetPayloadSize is a helper function to get the number of bytes of a - // specific FUSE operation struct. - size_t GetPayloadSize(uint32_t opcode, bool in); + // Handles FUSE request sent to /dev/fuse by its saved responses. + void ServerProcessFuseRequest(); + + // Responds to FUSE request with a saved data. + void ServerRespondFuseSuccess(FuseMemBuffer& mem_buf, + const FuseMemBlock& block, uint64_t unique); + + // Responds an error header to /dev/fuse when bad thing happens. + void ServerRespondFuseError(uint64_t unique); int dev_fd_; - int set_expected_[2]; - int done_[2]; - - std::vector<char> buf_; - std::vector<char> mem_in_; - std::vector<char> mem_out_; - ssize_t len_in_; - ssize_t len_out_; + int sock_[2]; + + FuseMemBuffer requests_; + FuseMemBuffer responses_; }; } // namespace testing |