summaryrefslogtreecommitdiffhomepage
path: root/test/fuse/linux/fuse_base.h
diff options
context:
space:
mode:
authorgVisor bot <gvisor-bot@google.com>2020-09-16 17:12:57 -0700
committergVisor bot <gvisor-bot@google.com>2020-09-16 17:12:57 -0700
commitae59e529791e4763aaa25eee818605f17699fc67 (patch)
treef1b90e46cfea394dcc48bb51781c5782b93c3ac8 /test/fuse/linux/fuse_base.h
parent286830855552efb223afa500fbcfa532f33121c5 (diff)
parentc4c302a27e5811ce04bb904686e657c9ed830a44 (diff)
Merge pull request #3934 from avagin:feature/fuse
PiperOrigin-RevId: 332122081
Diffstat (limited to 'test/fuse/linux/fuse_base.h')
-rw-r--r--test/fuse/linux/fuse_base.h240
1 files changed, 196 insertions, 44 deletions
diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h
index 3a2f255a9..6ad296ca2 100644
--- a/test/fuse/linux/fuse_base.h
+++ b/test/fuse/linux/fuse_base.h
@@ -16,8 +16,12 @@
#define GVISOR_TEST_FUSE_FUSE_BASE_H_
#include <linux/fuse.h>
+#include <string.h>
+#include <sys/stat.h>
#include <sys/uio.h>
+#include <iostream>
+#include <unordered_map>
#include <vector>
#include "gtest/gtest.h"
@@ -29,68 +33,216 @@ namespace testing {
constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0";
-class FuseTest : public ::testing::Test {
+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 {
+ kSetResponse = 0,
+ kSetInodeLookup,
+ kGetRequest,
+ kGetNumUnconsumedRequests,
+ kGetNumUnsentResponses,
+ kGetTotalReceivedBytes,
+ kSkipRequest,
+};
+
+// 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);
}
- 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);
+ // 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_;
+};
- // 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);
+// 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:
+ // nodeid_ is the ID of a fake inode. We starts from 2 since 1 is occupied by
+ // the mount point.
+ FuseTest() : nodeid_(2) {}
+ void SetUp() override;
+ void TearDown() override;
- // 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 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);
+
+ // Called by the testing thread to install a fake path under the mount point.
+ // e.g. a file under /mnt/dir/file and moint point is /mnt, then it will look
+ // up "dir/file" in this case.
+ //
+ // It sets a fixed response to the FUSE_LOOKUP requests issued with this
+ // 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,
+ 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
+ // data from server.
+ void GetServerActualRequest(std::vector<struct iovec>& iovecs);
+
+ // Called by the testing thread to query the number of unconsumed requests in
+ // the requests_ serial buffer of the FUSE server. TearDown() ensures all
+ // FUSE requests received by the FUSE server were consumed by the testing
+ // thread.
+ uint32_t GetServerNumUnconsumedRequests();
+
+ // Called by the testing thread to query the number of unsent responses in
+ // the responses_ serial buffer of the FUSE server. TearDown() ensures all
+ // preset FUSE responses were sent out by the FUSE server.
+ uint32_t GetServerNumUnsentResponses();
+
+ // Called by the testing thread to ask the FUSE server for its total received
+ // bytes from /dev/fuse.
+ uint32_t GetServerTotalReceivedBytes();
+
+ // Called by the testing thread to ask the FUSE server to skip stored
+ // request data.
+ void SkipServerActualRequest();
protected:
TempPath mount_point_;
- private:
- void MountFuse();
+ // Opens /dev/fuse and inherit the file descriptor for the FUSE server.
+ void MountFuse(const char* mountOpts = kMountOpts);
+
+ // Creates a socketpair for communication and forks FUSE server.
+ void SetUpFuseServer(
+ const struct fuse_init_out* payload = &kDefaultFUSEInitOutPayload);
+
+ // Unmounts the mountpoint of the FUSE server.
void UnmountFuse();
- // ConsumeFuseInit is only used during FUSE server setup.
- PosixError ConsumeFuseInit();
+ private:
+ // Sends a FuseTestCmd and gets a uint32_t data from the FUSE server.
+ inline uint32_t GetServerData(uint32_t cmd);
+
+ // Waits for FUSE server to complete its processing. Complains if the FUSE
+ // server responds any failure during tests.
+ void WaitServerComplete();
- // ReceiveExpected is the FUSE server side's corresponding code of
- // `SetExpected()`. Save the request-response pair into its memory.
- void ReceiveExpected();
+ // The FUSE server stays here and waits next command or FUSE request until it
+ // is terminated.
+ void ServerFuseLoop();
- // MarkDone is used by the FUSE server to tell testing main if it's OK to
- // proceed next command.
- void MarkDone(bool success);
+ // 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);
- // FuseLoop is where the FUSE server stay until it is terminated.
- void FuseLoop();
+ // Consumes the first FUSE request when mounting FUSE. Replies with a
+ // response with empty payload.
+ PosixError ServerConsumeFuseInit(const struct fuse_init_out* payload);
- // SetUpFuseServer creates 2 pipes for communication and forks FUSE server.
- void SetUpFuseServer();
+ // A command switch that dispatch different FuseTestCmd to its handler.
+ void ServerHandleCommand();
- // 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);
+ // 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 `SetServerInodeLookup()`.
+ // Handles `kSetInodeLookup` command. Receives an expected file mode and
+ // file path under the mount point.
+ void ServerReceiveInodeLookup();
+
+ // The FUSE server side's corresponding code of `GetServerActualRequest()`.
+ // Handles `kGetRequest` command. Sends the next received request pointed by
+ // the cursor.
+ void ServerSendReceivedRequest();
+
+ // Sends a uint32_t data via socket.
+ inline void ServerSendData(uint32_t data);
+
+ // The FUSE server side's corresponding code of `SkipServerActualRequest()`.
+ // Handles `kSkipRequest` command. Skip the request pointed by current cursor.
+ void ServerSkipReceivedRequest();
+
+ // 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];
+
+ uint64_t nodeid_;
+ std::unordered_map<std::string, FuseMemBlock> lookup_map_;
+
+ FuseMemBuffer requests_;
+ FuseMemBuffer responses_;
+ FuseMemBuffer lookups_;
};
} // namespace testing