2020-08-04 19:27:55 +00:00
|
|
|
// 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>
|
2020-08-17 22:33:19 +00:00
|
|
|
#include <string.h>
|
2020-08-17 17:05:10 +00:00
|
|
|
#include <sys/stat.h>
|
2020-08-04 19:27:55 +00:00
|
|
|
#include <sys/uio.h>
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
#include <iostream>
|
2020-08-17 17:05:10 +00:00
|
|
|
#include <unordered_map>
|
2020-08-04 19:27:55 +00:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "test/util/posix_error.h"
|
2020-08-11 01:15:32 +00:00
|
|
|
#include "test/util/temp_path.h"
|
2020-08-04 19:27:55 +00:00
|
|
|
|
|
|
|
namespace gvisor {
|
|
|
|
namespace testing {
|
|
|
|
|
|
|
|
constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0";
|
|
|
|
|
2020-09-01 01:49:57 +00:00
|
|
|
constexpr struct fuse_init_out kDefaultFUSEInitOutPayload = {.major = 7};
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// 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,
|
2020-08-17 17:05:10 +00:00
|
|
|
kSetInodeLookup,
|
2020-08-17 22:33:19 +00:00
|
|
|
kGetRequest,
|
2020-08-14 16:54:35 +00:00
|
|
|
kGetNumUnconsumedRequests,
|
|
|
|
kGetNumUnsentResponses,
|
|
|
|
kGetTotalReceivedBytes,
|
2020-08-18 01:46:39 +00:00
|
|
|
kSkipRequest,
|
2020-08-17 22:33:19 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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 {
|
2020-08-04 19:27:55 +00:00
|
|
|
public:
|
2020-08-17 22:33:19 +00:00
|
|
|
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);
|
2020-08-04 19:27:55 +00:00
|
|
|
}
|
2020-08-17 22:33:19 +00:00
|
|
|
|
|
|
|
// 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:
|
2020-08-17 17:05:10 +00:00
|
|
|
// nodeid_ is the ID of a fake inode. We starts from 2 since 1 is occupied by
|
|
|
|
// the mount point.
|
|
|
|
FuseTest() : nodeid_(2) {}
|
2020-08-04 19:27:55 +00:00
|
|
|
void SetUp() override;
|
|
|
|
void TearDown() override;
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// 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);
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-08-17 17:05:10 +00:00
|
|
|
// 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);
|
2020-08-17 17:05:10 +00:00
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// 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);
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-08-14 16:54:35 +00:00
|
|
|
// 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();
|
|
|
|
|
2020-08-18 01:46:39 +00:00
|
|
|
// Called by the testing thread to ask the FUSE server to skip stored
|
|
|
|
// request data.
|
|
|
|
void SkipServerActualRequest();
|
|
|
|
|
2020-08-11 01:15:32 +00:00
|
|
|
protected:
|
|
|
|
TempPath mount_point_;
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// 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);
|
2020-08-17 22:33:19 +00:00
|
|
|
|
|
|
|
// Creates a socketpair for communication and forks FUSE server.
|
2020-09-01 01:49:57 +00:00
|
|
|
void SetUpFuseServer(
|
|
|
|
const struct fuse_init_out* payload = &kDefaultFUSEInitOutPayload);
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-07-15 18:32:51 +00:00
|
|
|
// Unmounts the mountpoint of the FUSE server.
|
|
|
|
void UnmountFuse();
|
|
|
|
|
|
|
|
private:
|
2020-08-14 16:54:35 +00:00
|
|
|
// Sends a FuseTestCmd and gets a uint32_t data from the FUSE server.
|
|
|
|
inline uint32_t GetServerData(uint32_t cmd);
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// Waits for FUSE server to complete its processing. Complains if the FUSE
|
|
|
|
// server responds any failure during tests.
|
|
|
|
void WaitServerComplete();
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// The FUSE server stays here and waits next command or FUSE request until it
|
|
|
|
// is terminated.
|
|
|
|
void ServerFuseLoop();
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// 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);
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// Consumes the first FUSE request when mounting FUSE. Replies with a
|
|
|
|
// response with empty payload.
|
2020-09-01 01:49:57 +00:00
|
|
|
PosixError ServerConsumeFuseInit(const struct fuse_init_out* payload);
|
2020-08-17 22:33:19 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
2020-08-17 17:05:10 +00:00
|
|
|
// 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();
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// The FUSE server side's corresponding code of `GetServerActualRequest()`.
|
|
|
|
// Handles `kGetRequest` command. Sends the next received request pointed by
|
|
|
|
// the cursor.
|
|
|
|
void ServerSendReceivedRequest();
|
2020-08-04 19:27:55 +00:00
|
|
|
|
2020-08-14 16:54:35 +00:00
|
|
|
// Sends a uint32_t data via socket.
|
|
|
|
inline void ServerSendData(uint32_t data);
|
|
|
|
|
2020-08-18 01:46:39 +00:00
|
|
|
// The FUSE server side's corresponding code of `SkipServerActualRequest()`.
|
|
|
|
// Handles `kSkipRequest` command. Skip the request pointed by current cursor.
|
|
|
|
void ServerSkipReceivedRequest();
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
// 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);
|
2020-08-04 19:27:55 +00:00
|
|
|
|
|
|
|
int dev_fd_;
|
2020-08-17 22:33:19 +00:00
|
|
|
int sock_[2];
|
|
|
|
|
2020-08-17 17:05:10 +00:00
|
|
|
uint64_t nodeid_;
|
|
|
|
std::unordered_map<std::string, FuseMemBlock> lookup_map_;
|
|
|
|
|
2020-08-17 22:33:19 +00:00
|
|
|
FuseMemBuffer requests_;
|
|
|
|
FuseMemBuffer responses_;
|
2020-08-17 17:05:10 +00:00
|
|
|
FuseMemBuffer lookups_;
|
2020-08-04 19:27:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace testing
|
|
|
|
} // namespace gvisor
|
|
|
|
|
|
|
|
#endif // GVISOR_TEST_FUSE_FUSE_BASE_H_
|