gvisor/test/fuse/linux/fuse_base.h

252 lines
8.9 KiB
C
Raw Normal View History

// 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.
#ifndef GVISOR_TEST_FUSE_FUSE_BASE_H_
#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"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
namespace gvisor {
namespace testing {
constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0";
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:
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:
// 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;
// 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,
2020-07-15 18:32:51 +00:00
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_;
// Opens /dev/fuse and inherit the file descriptor for the FUSE server.
2020-07-15 18:32:51 +00:00
void MountFuse(const char* mountOpts = kMountOpts);
// Creates a socketpair for communication and forks FUSE server.
void SetUpFuseServer(
const struct fuse_init_out* payload = &kDefaultFUSEInitOutPayload);
2020-07-15 18:32:51 +00:00
// Unmounts the mountpoint of the FUSE server.
void UnmountFuse();
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();
// The FUSE server stays here and waits next command or FUSE request until it
// is terminated.
void ServerFuseLoop();
// 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);
// Consumes the first FUSE request when mounting FUSE. Replies with a
// response with empty payload.
PosixError ServerConsumeFuseInit(const struct fuse_init_out* payload);
// 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 `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 sock_[2];
uint64_t nodeid_;
std::unordered_map<std::string, FuseMemBlock> lookup_map_;
FuseMemBuffer requests_;
FuseMemBuffer responses_;
FuseMemBuffer lookups_;
};
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_FUSE_FUSE_BASE_H_