diff options
author | Craig Chi <craigchi@google.com> | 2020-08-17 10:05:10 -0700 |
---|---|---|
committer | Andrei Vagin <avagin@gmail.com> | 2020-09-16 12:19:30 -0700 |
commit | 717b661c457cc3a125fcdfd133b633ca48545541 (patch) | |
tree | d4cb58a8b8c90460883424066d77befb048e766a | |
parent | d928d3c00a66a29933eee9671e3558cd8163337f (diff) |
Add function to create a fake inode in FUSE integration test
Adds a function for the testing thread to set up a fake inode with a
specific path under mount point. After this function is called, each
subsequent FUSE_LOOKUP request with the same path will be served with
the fixed stub response.
Fixes #3539
-rw-r--r-- | test/fuse/linux/fuse_base.cc | 91 | ||||
-rw-r--r-- | test/fuse/linux/fuse_base.h | 25 |
2 files changed, 116 insertions, 0 deletions
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc index c354e1dcb..a9fe1044e 100644 --- a/test/fuse/linux/fuse_base.cc +++ b/test/fuse/linux/fuse_base.cc @@ -117,6 +117,23 @@ uint32_t FuseTest::GetServerTotalReceivedBytes() { static_cast<uint32_t>(FuseTestCmd::kGetTotalReceivedBytes)); } +// 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) { + 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))); + + // 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() { EXPECT_THAT(dev_fd_ = open("/dev/fuse", O_RDWR), SyscallSucceeds()); @@ -252,6 +269,9 @@ void FuseTest::ServerHandleCommand() { case FuseTestCmd::kSetResponse: ServerReceiveResponse(); break; + case FuseTestCmd::kSetInodeLookup: + ServerReceiveInodeLookup(); + break; case FuseTestCmd::kGetRequest: ServerSendReceivedRequest(); break; @@ -272,6 +292,64 @@ void FuseTest::ServerHandleCommand() { 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; + 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], 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 = { + .nodeid = nodeid_, + .generation = 0, + .entry_valid = 0, + .attr_valid = 0, + .entry_valid_nsec = 0, + .attr_valid_nsec = 0, + .attr = + (struct fuse_attr){ + .ino = nodeid_, + .size = 512, + .blocks = 4, + .atime = 0, + .mtime = 0, + .ctime = 0, + .atimensec = 0, + .mtimensec = 0, + .ctimensec = 0, + .mode = mode, + .nlink = 2, + .uid = 1234, + .gid = 4321, + .rdev = 12, + .blksize = 4096, + }, + }; + // Since this is only used in test, nodeid_ is simply increased by 1 to + // comply with the unqiueness of different path. + ++nodeid_; + + 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()) { @@ -297,6 +375,19 @@ void FuseTest::ServerProcessFuseRequest() { 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); // Check if there is a corresponding response. diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h index 3f2522977..a21b4bb8d 100644 --- a/test/fuse/linux/fuse_base.h +++ b/test/fuse/linux/fuse_base.h @@ -17,9 +17,11 @@ #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" @@ -35,6 +37,7 @@ constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0"; // server. See test/fuse/README.md for further detail. enum class FuseTestCmd { kSetResponse = 0, + kSetInodeLookup, kGetRequest, kGetNumUnconsumedRequests, kGetNumUnsentResponses, @@ -114,6 +117,9 @@ class FuseMemBuffer { // 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; @@ -122,6 +128,16 @@ class FuseTest : public ::testing::Test { // 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); + // 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. @@ -182,6 +198,11 @@ class FuseTest : public ::testing::Test { // 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. @@ -203,8 +224,12 @@ class FuseTest : public ::testing::Test { int dev_fd_; int sock_[2]; + uint64_t nodeid_; + std::unordered_map<std::string, FuseMemBlock> lookup_map_; + FuseMemBuffer requests_; FuseMemBuffer responses_; + FuseMemBuffer lookups_; }; } // namespace testing |