mirror of
https://github.com/facebook/sapling.git
synced 2024-10-04 22:07:44 +03:00
Set up bind mounts for a client when mounting it.
Summary: This adds a new API to `PrivHelper`: `privilegedBindMount()`. Similar to `privilegedFuseMount()`, this sends a message to the privileged helper, which is running as `root`, so it can set up the specified bind mount. The changes in the `privhelper` directory parrot what was done to support `privilegedFuseMount()`. Now, once the primary mount for a client is created, any bind mounts listed in the config for the client are set up. This logic is introduced in `EdenServer.cpp`. Reviewed By: simpkins Differential Revision: D3296660 fbshipit-source-id: 61296f35e5c3a6f232a1c17e0f296dd5d3b5ec06
This commit is contained in:
parent
20ce44db52
commit
d6d5d6c695
@ -27,6 +27,7 @@ def create_eden_fs_rules(suffix, subdir, server_srcs, server_deps):
|
||||
":thrift-cpp2",
|
||||
"@/common/fb303/cpp:fb303",
|
||||
"@/eden/fuse:fusell",
|
||||
"@/eden/fuse/privhelper:privhelper",
|
||||
"@/eden/fs/config:config",
|
||||
"@/eden/fs/inodes:inodes",
|
||||
"@/folly/experimental:experimental",
|
||||
|
@ -9,18 +9,21 @@
|
||||
*/
|
||||
#include "EdenServer.h"
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <folly/SocketAddress.h>
|
||||
#include <folly/String.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <thrift/lib/cpp2/server/ThriftServer.h>
|
||||
#include <wangle/concurrent/CPUThreadPoolExecutor.h>
|
||||
#include <wangle/concurrent/GlobalExecutor.h>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include "EdenServiceHandler.h"
|
||||
#include "eden/fs/config/ClientConfig.h"
|
||||
#include "eden/fs/inodes/EdenMount.h"
|
||||
#include "eden/fs/store/LocalStore.h"
|
||||
#include "eden/fuse/MountPoint.h"
|
||||
#include "eden/fuse/privhelper/PrivHelper.h"
|
||||
|
||||
DEFINE_bool(debug, false, "run fuse in debug mode");
|
||||
|
||||
@ -72,7 +75,9 @@ void EdenServer::run() {
|
||||
runThriftServer();
|
||||
}
|
||||
|
||||
void EdenServer::mount(std::shared_ptr<EdenMount> edenMount) {
|
||||
void EdenServer::mount(
|
||||
std::shared_ptr<EdenMount> edenMount,
|
||||
std::unique_ptr<ClientConfig> config) {
|
||||
// Add the mount point to mountPoints_.
|
||||
// This also makes sure we don't have this path mounted already
|
||||
auto mountPath = edenMount->getPath().stringPiece();
|
||||
@ -98,6 +103,26 @@ void EdenServer::mount(std::shared_ptr<EdenMount> edenMount) {
|
||||
this->mountFinished(edenMount.get());
|
||||
throw;
|
||||
}
|
||||
|
||||
// Perform all of the bind mounts associated with the client.
|
||||
for (auto bindMount : config->getBindMounts()) {
|
||||
auto pathInMountDir = bindMount.pathInMountDir;
|
||||
try {
|
||||
// If pathInMountDir does not exist, then it must be created before the
|
||||
// bind mount is performed.
|
||||
boost::system::error_code errorCode;
|
||||
boost::filesystem::path mountDir = pathInMountDir.c_str();
|
||||
boost::filesystem::create_directories(mountDir, errorCode);
|
||||
|
||||
fusell::privilegedBindMount(
|
||||
bindMount.pathInClientDir.c_str(), pathInMountDir.c_str());
|
||||
} catch (...) {
|
||||
// Consider recording all failed bind mounts in a way that can be
|
||||
// communicated back to the caller in a structured way.
|
||||
LOG(ERROR) << "Failed to perform bind mount for "
|
||||
<< pathInMountDir.stringPiece() << ".";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EdenServer::mountFinished(EdenMount* edenMount) {
|
||||
|
@ -26,6 +26,7 @@ class ThriftServer;
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
class ClientConfig;
|
||||
class EdenMount;
|
||||
class EdenServiceHandler;
|
||||
class LocalStore;
|
||||
@ -47,7 +48,10 @@ class EdenServer {
|
||||
|
||||
void run();
|
||||
|
||||
void mount(std::shared_ptr<EdenMount> edenMount);
|
||||
void mount(
|
||||
std::shared_ptr<EdenMount> edenMount,
|
||||
std::unique_ptr<ClientConfig> config);
|
||||
|
||||
void unmount(folly::StringPiece mountPath);
|
||||
|
||||
const std::shared_ptr<EdenServiceHandler>& getHandler() const {
|
||||
|
@ -57,7 +57,7 @@ void EdenServiceHandler::mountImpl(const MountInfo& info) {
|
||||
|
||||
// TODO(mbolin): Use the result of config.getBindMounts() to perform the
|
||||
// appropriate bind mounts for the client.
|
||||
server_->mount(std::move(edenMount));
|
||||
server_->mount(std::move(edenMount), std::move(config));
|
||||
}
|
||||
|
||||
void EdenServiceHandler::mount(std::unique_ptr<MountInfo> info) {
|
||||
|
@ -157,10 +157,20 @@ folly::File privilegedFuseMount(folly::StringPiece mountPath) {
|
||||
|
||||
folly::File file;
|
||||
gPrivHelper->sendAndRecv(&msg, &file);
|
||||
PrivHelperConn::parseMountResponse(&msg);
|
||||
PrivHelperConn::parseEmptyResponse(&msg);
|
||||
CHECK(file) << "no file descriptor received in privhelper mount response";
|
||||
return file;
|
||||
}
|
||||
|
||||
void privilegedBindMount(
|
||||
folly::StringPiece clientPath,
|
||||
folly::StringPiece mountPath) {
|
||||
PrivHelperConn::Message msg;
|
||||
PrivHelperConn::serializeBindMountRequest(&msg, clientPath, mountPath);
|
||||
|
||||
gPrivHelper->sendAndRecv(&msg, nullptr);
|
||||
PrivHelperConn::parseEmptyResponse(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // facebook::eden::fusell
|
||||
|
@ -73,6 +73,15 @@ void dropPrivileges();
|
||||
* itself will just pick the right values.
|
||||
*/
|
||||
folly::File privilegedFuseMount(folly::StringPiece mountPath);
|
||||
|
||||
/*
|
||||
* @param clientPath Absolute path (that should be under
|
||||
* .eden/clients/<client-name>/bind-mounts/) where the "real" storage is.
|
||||
* @param mountPath Absolute path where the bind mount should be applied.
|
||||
*/
|
||||
void privilegedBindMount(
|
||||
folly::StringPiece clientPath,
|
||||
folly::StringPiece mountPath);
|
||||
}
|
||||
}
|
||||
} // facebook::eden::fusell
|
||||
|
@ -264,7 +264,7 @@ void PrivHelperConn::recvMsg(Message* msg, folly::File* f) {
|
||||
void PrivHelperConn::serializeMountRequest(
|
||||
Message* msg,
|
||||
StringPiece mountPoint) {
|
||||
msg->msgType = REQ_MOUNT;
|
||||
msg->msgType = REQ_MOUNT_FUSE;
|
||||
IOBuf buf{IOBuf::WRAP_BUFFER, msg->data, sizeof(msg->data)};
|
||||
buf.clear(); // Mark all the buffer space as unused
|
||||
Appender a{&buf, 0};
|
||||
@ -276,7 +276,7 @@ void PrivHelperConn::serializeMountRequest(
|
||||
}
|
||||
|
||||
void PrivHelperConn::parseMountRequest(Message* msg, string& mountPoint) {
|
||||
CHECK_EQ(msg->msgType, REQ_MOUNT);
|
||||
CHECK_EQ(msg->msgType, REQ_MOUNT_FUSE);
|
||||
CHECK_LE(msg->dataSize, sizeof(msg->data));
|
||||
|
||||
IOBuf buf{IOBuf::WRAP_BUFFER, msg->data, msg->dataSize};
|
||||
@ -286,20 +286,54 @@ void PrivHelperConn::parseMountRequest(Message* msg, string& mountPoint) {
|
||||
mountPoint = c.readFixedString(size);
|
||||
}
|
||||
|
||||
void PrivHelperConn::serializeMountResponse(Message* msg) {
|
||||
msg->msgType = RESP_MOUNT;
|
||||
void PrivHelperConn::serializeEmptyResponse(Message* msg) {
|
||||
msg->msgType = RESP_EMPTY;
|
||||
msg->dataSize = 0;
|
||||
}
|
||||
|
||||
void PrivHelperConn::parseMountResponse(const Message* msg) {
|
||||
void PrivHelperConn::parseEmptyResponse(const Message* msg) {
|
||||
if (msg->msgType == RESP_ERROR) {
|
||||
rethrowErrorResponse(msg);
|
||||
} else if (msg->msgType != RESP_MOUNT) {
|
||||
} else if (msg->msgType != RESP_EMPTY) {
|
||||
throw std::runtime_error(
|
||||
folly::to<string>("unexpected response type: ", msg->msgType));
|
||||
}
|
||||
}
|
||||
|
||||
void PrivHelperConn::serializeBindMountRequest(
|
||||
Message* msg,
|
||||
folly::StringPiece clientPath,
|
||||
folly::StringPiece mountPath) {
|
||||
msg->msgType = REQ_MOUNT_BIND;
|
||||
IOBuf buf{IOBuf::WRAP_BUFFER, msg->data, sizeof(msg->data)};
|
||||
buf.clear(); // Mark all the buffer space as unused
|
||||
Appender a{&buf, 0};
|
||||
|
||||
a.writeBE<uint32_t>(clientPath.size());
|
||||
a.push(ByteRange(clientPath));
|
||||
|
||||
a.writeBE<uint32_t>(mountPath.size());
|
||||
a.push(ByteRange(mountPath));
|
||||
|
||||
msg->dataSize = buf.length();
|
||||
}
|
||||
|
||||
void PrivHelperConn::parseBindMountRequest(
|
||||
Message* msg,
|
||||
std::string& clientPath,
|
||||
std::string& mountPath) {
|
||||
CHECK_EQ(msg->msgType, REQ_MOUNT_BIND);
|
||||
CHECK_LE(msg->dataSize, sizeof(msg->data));
|
||||
|
||||
IOBuf buf{IOBuf::WRAP_BUFFER, msg->data, msg->dataSize};
|
||||
Cursor c{&buf};
|
||||
|
||||
auto clientPathSize = c.readBE<uint32_t>();
|
||||
clientPath = c.readFixedString(clientPathSize);
|
||||
auto mountPathSize = c.readBE<uint32_t>();
|
||||
mountPath = c.readFixedString(mountPathSize);
|
||||
}
|
||||
|
||||
void PrivHelperConn::serializeErrorResponse(
|
||||
Message* msg,
|
||||
const std::exception& ex) {
|
||||
|
@ -39,8 +39,9 @@ class PrivHelperConn {
|
||||
enum MsgType : uint32_t {
|
||||
MSG_TYPE_NONE = 0,
|
||||
RESP_ERROR = 1,
|
||||
REQ_MOUNT = 2,
|
||||
RESP_MOUNT = 3,
|
||||
RESP_EMPTY = 2,
|
||||
REQ_MOUNT_FUSE = 3,
|
||||
REQ_MOUNT_BIND = 4,
|
||||
};
|
||||
|
||||
struct Message {
|
||||
@ -123,10 +124,22 @@ class PrivHelperConn {
|
||||
folly::StringPiece mountPoint);
|
||||
static void parseMountRequest(Message* msg, std::string& mountPoint);
|
||||
|
||||
static void serializeMountResponse(Message* msg);
|
||||
// Parse a mount response.
|
||||
// Will throw an exception if this is actually an error response.
|
||||
static void parseMountResponse(const Message* msg);
|
||||
static void serializeBindMountRequest(
|
||||
Message* msg,
|
||||
folly::StringPiece clientPath,
|
||||
folly::StringPiece mountPath);
|
||||
static void parseBindMountRequest(
|
||||
Message* msg,
|
||||
std::string& clientPath,
|
||||
std::string& mountPath);
|
||||
|
||||
static void serializeEmptyResponse(Message* msg);
|
||||
|
||||
/**
|
||||
* Parse a response that is expected to be empty.
|
||||
* Will throw an exception if this is actually an error response.
|
||||
*/
|
||||
static void parseEmptyResponse(const Message* msg);
|
||||
|
||||
static void serializeErrorResponse(Message* msg, const std::exception& ex);
|
||||
static void serializeErrorResponse(
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#include "PrivHelperServer.h"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Exception.h>
|
||||
#include <folly/File.h>
|
||||
@ -19,6 +20,7 @@
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/vfs.h>
|
||||
#include <unistd.h>
|
||||
#include <set>
|
||||
|
||||
@ -87,6 +89,14 @@ folly::File PrivHelperServer::fuseMount(const char* mountPath) {
|
||||
return fuseDev;
|
||||
}
|
||||
|
||||
void PrivHelperServer::bindMount(
|
||||
const char* clientPath,
|
||||
const char* mountPath) {
|
||||
int rc = mount(
|
||||
clientPath, mountPath, /* type */ nullptr, MS_BIND, /* data */ nullptr);
|
||||
checkUnixError(rc, "failed to mount");
|
||||
}
|
||||
|
||||
void PrivHelperServer::fuseUnmount(const char* mountPath) {
|
||||
auto rc = umount2(mountPath, UMOUNT_NOFOLLOW);
|
||||
if (rc != 0) {
|
||||
@ -109,7 +119,7 @@ void PrivHelperServer::processMountMsg(PrivHelperConn::Message* msg) {
|
||||
try {
|
||||
fuseDev = fuseMount(mountPath.c_str());
|
||||
mountPoints_.insert(mountPath);
|
||||
conn_.serializeMountResponse(msg);
|
||||
conn_.serializeEmptyResponse(msg);
|
||||
} catch (const std::exception& ex) {
|
||||
// Note that we re-use the request message buffer for the response data
|
||||
conn_.serializeErrorResponse(msg, ex);
|
||||
@ -121,13 +131,50 @@ void PrivHelperServer::processMountMsg(PrivHelperConn::Message* msg) {
|
||||
conn_.sendMsg(msg, fuseDev.fd());
|
||||
}
|
||||
|
||||
void PrivHelperServer::processBindMountMsg(PrivHelperConn::Message* msg) {
|
||||
string clientPath;
|
||||
string mountPath;
|
||||
conn_.parseBindMountRequest(msg, clientPath, mountPath);
|
||||
|
||||
// Figure out which FUSE mount the mountPath belongs to.
|
||||
// (Alternatively, we could just make this part of the Message.)
|
||||
string key;
|
||||
for (const auto& mountPoint : mountPoints_) {
|
||||
if (boost::starts_with(mountPath, mountPoint + "/")) {
|
||||
key = mountPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (key.empty()) {
|
||||
throw std::domain_error(
|
||||
folly::to<string>("No FUSE mount found for ", mountPath));
|
||||
}
|
||||
|
||||
try {
|
||||
bindMount(clientPath.c_str(), mountPath.c_str());
|
||||
bindMountPoints_.insert({key, mountPath});
|
||||
conn_.serializeEmptyResponse(msg);
|
||||
} catch (const std::exception& ex) {
|
||||
// Note that we re-use the request message buffer for the response data
|
||||
conn_.serializeErrorResponse(msg, ex);
|
||||
conn_.sendMsg(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that we re-use the request message buffer for the response data
|
||||
conn_.sendMsg(msg);
|
||||
}
|
||||
|
||||
void PrivHelperServer::messageLoop() {
|
||||
PrivHelperConn::Message msg;
|
||||
|
||||
while (1) {
|
||||
conn_.recvMsg(&msg, nullptr);
|
||||
if (msg.msgType == PrivHelperConn::REQ_MOUNT) {
|
||||
auto msgType = msg.msgType;
|
||||
if (msgType == PrivHelperConn::REQ_MOUNT_FUSE) {
|
||||
processMountMsg(&msg);
|
||||
} else if (msgType == PrivHelperConn::REQ_MOUNT_BIND) {
|
||||
processBindMountMsg(&msg);
|
||||
} else {
|
||||
// This shouldn't ever happen unless we have a bug.
|
||||
// Crash if it does occur. (We could send back an error message and
|
||||
@ -139,12 +186,47 @@ void PrivHelperServer::messageLoop() {
|
||||
}
|
||||
|
||||
void PrivHelperServer::cleanupMountPoints() {
|
||||
for (const auto& mp : mountPoints_) {
|
||||
fuseUnmount(mp.c_str());
|
||||
int numBindMountsRemoved = 0;
|
||||
for (const auto& mountPoint : mountPoints_) {
|
||||
// Clean up the bind mounts for a FUSE mount before the FUSE mount itself.
|
||||
auto range = bindMountPoints_.equal_range(mountPoint);
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
auto bindMount = it->second;
|
||||
auto path = bindMount.c_str();
|
||||
bindUnmount(bindMount.c_str());
|
||||
numBindMountsRemoved++;
|
||||
}
|
||||
|
||||
// This appears to fail sometimes with "Device or resource busy" if a
|
||||
// terminal is still open with the mountPoint as the working directory.
|
||||
fuseUnmount(mountPoint.c_str());
|
||||
}
|
||||
|
||||
CHECK_EQ(bindMountPoints_.size(), numBindMountsRemoved)
|
||||
<< "All bind mounts should have been removed.";
|
||||
bindMountPoints_.clear();
|
||||
mountPoints_.clear();
|
||||
}
|
||||
|
||||
void PrivHelperServer::bindUnmount(const char* mountPath) {
|
||||
fuseUnmount(mountPath);
|
||||
|
||||
// Empirically, the unmount may not be complete when umount2() returns.
|
||||
// To work around this, we repeatedly invoke statfs on the bind mount
|
||||
// until it fails, demonstrating that it has finished unmounting.
|
||||
struct statfs st;
|
||||
int rc;
|
||||
while (true) {
|
||||
// This should have a non-zero exit code once the path is unmounted.
|
||||
rc = statfs(mountPath, &st);
|
||||
if (rc == 0) {
|
||||
sched_yield();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrivHelperServer::run() {
|
||||
// Ignore SIGINT and SIGTERM.
|
||||
// We should only exit when our parent process does.
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "PrivHelperConn.h"
|
||||
|
||||
namespace folly {
|
||||
@ -46,10 +47,14 @@ class PrivHelperServer {
|
||||
void messageLoop();
|
||||
void cleanupMountPoints();
|
||||
void processMountMsg(PrivHelperConn::Message* msg);
|
||||
void processBindMountMsg(PrivHelperConn::Message* msg);
|
||||
|
||||
// These methods are virtual so we can override them during unit tests
|
||||
virtual folly::File fuseMount(const char* mountPath);
|
||||
virtual void fuseUnmount(const char* mountPath);
|
||||
// Both clientPath and mountPath must be existing directories.
|
||||
virtual void bindMount(const char* clientPath, const char* mountPath);
|
||||
virtual void bindUnmount(const char* mountPath);
|
||||
|
||||
PrivHelperConn conn_;
|
||||
uid_t uid_{std::numeric_limits<uid_t>::max()};
|
||||
@ -58,6 +63,7 @@ class PrivHelperServer {
|
||||
// The privhelper server only has a single thread,
|
||||
// so we don't need to lock the following state
|
||||
std::set<std::string> mountPoints_;
|
||||
std::unordered_multimap<std::string, std::string> bindMountPoints_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,7 @@ cpp_library(
|
||||
"@/folly/io:iobuf",
|
||||
"@/folly:folly",
|
||||
],
|
||||
external_deps = [
|
||||
("boost", None, "boost_filesystem"),
|
||||
],
|
||||
)
|
||||
|
@ -11,8 +11,10 @@
|
||||
#include "eden/fuse/privhelper/PrivHelper.h"
|
||||
#include "eden/fuse/privhelper/PrivHelperConn.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <folly/Exception.h>
|
||||
#include <folly/File.h>
|
||||
#include <folly/FileUtil.h>
|
||||
#include <folly/Range.h>
|
||||
#include <folly/experimental/TestUtil.h>
|
||||
#include <folly/io/IOBuf.h>
|
||||
@ -206,8 +208,8 @@ TEST(PrivHelper, SerializeError) {
|
||||
|
||||
// Try parsing it as a mount response
|
||||
try {
|
||||
PrivHelperConn::parseMountResponse(&msg);
|
||||
FAIL() << "expected parseMountResponse() to throw";
|
||||
PrivHelperConn::parseEmptyResponse(&msg);
|
||||
FAIL() << "expected parseEmptyResponse() to throw";
|
||||
} catch (const std::system_error& ex) {
|
||||
EXPECT_EQ(std::system_category(), ex.code().category());
|
||||
EXPECT_EQ(ENOENT, ex.code().value());
|
||||
@ -218,7 +220,23 @@ TEST(PrivHelper, SerializeError) {
|
||||
|
||||
TEST(PrivHelper, ServerShutdownTest) {
|
||||
TemporaryDirectory tmpDir;
|
||||
PrivHelperTestServer server(tmpDir.path().string());
|
||||
PrivHelperTestServer server;
|
||||
|
||||
auto fooDir = tmpDir.path() / "foo";
|
||||
create_directory(fooDir);
|
||||
auto foo = fooDir.string();
|
||||
|
||||
// Note we do not create this directory explicitly because we want to verify
|
||||
// that privilegedBindMount takes care of this for us.
|
||||
auto mountedBuckOut = tmpDir.path() / "foo" / "buck-out";
|
||||
|
||||
auto barDir = tmpDir.path() / "bar";
|
||||
create_directory(barDir);
|
||||
auto bar = barDir.string();
|
||||
|
||||
auto otherDir = (tmpDir.path() / "other");
|
||||
create_directory(barDir);
|
||||
auto other = otherDir.string();
|
||||
|
||||
{
|
||||
startPrivHelper(&server, getuid(), getgid());
|
||||
@ -227,17 +245,27 @@ TEST(PrivHelper, ServerShutdownTest) {
|
||||
};
|
||||
|
||||
// Create a few mount points
|
||||
auto foo = privilegedFuseMount("foo");
|
||||
auto bar = privilegedFuseMount("bar");
|
||||
EXPECT_TRUE(server.isMounted("foo"));
|
||||
EXPECT_TRUE(server.isMounted("bar"));
|
||||
EXPECT_FALSE(server.isMounted("other"));
|
||||
privilegedFuseMount(foo);
|
||||
privilegedFuseMount(bar);
|
||||
EXPECT_TRUE(server.isMounted(foo));
|
||||
EXPECT_TRUE(server.isMounted(bar));
|
||||
EXPECT_FALSE(server.isMounted(other));
|
||||
|
||||
// Create a bind mount.
|
||||
EXPECT_FALSE(boost::filesystem::exists(mountedBuckOut));
|
||||
TemporaryDirectory realBuckOut;
|
||||
privilegedBindMount(realBuckOut.path().c_str(), mountedBuckOut.c_str());
|
||||
EXPECT_TRUE(server.isBindMounted(mountedBuckOut.c_str()));
|
||||
EXPECT_TRUE(boost::filesystem::exists(mountedBuckOut))
|
||||
<< "privilegedBindMount() should create the bind mount directory for "
|
||||
"the caller.";
|
||||
|
||||
// The privhelper will exit at the end of this scope
|
||||
}
|
||||
|
||||
// Make sure things get umounted when the privhelper quits
|
||||
EXPECT_FALSE(server.isMounted("foo"));
|
||||
EXPECT_FALSE(server.isMounted("bar"));
|
||||
EXPECT_FALSE(server.isMounted("other"));
|
||||
EXPECT_FALSE(server.isMounted(foo));
|
||||
EXPECT_FALSE(server.isBindMounted(mountedBuckOut.string()));
|
||||
EXPECT_FALSE(server.isMounted(bar));
|
||||
EXPECT_FALSE(server.isMounted(other));
|
||||
}
|
||||
|
@ -9,45 +9,88 @@
|
||||
*/
|
||||
#include "PrivHelperTestServer.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <folly/File.h>
|
||||
#include <folly/FileUtil.h>
|
||||
#include <system_error>
|
||||
|
||||
using folly::File;
|
||||
using folly::StringPiece;
|
||||
using std::string;
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
namespace fusell {
|
||||
|
||||
PrivHelperTestServer::PrivHelperTestServer(StringPiece tmpDir)
|
||||
: tmpDir_(tmpDir.str()) {}
|
||||
PrivHelperTestServer::PrivHelperTestServer() {}
|
||||
|
||||
// FUSE mounts.
|
||||
|
||||
File PrivHelperTestServer::fuseMount(const char* mountPath) {
|
||||
// Just open a new file inside our temporary directory,
|
||||
// and write "mounted" into it.
|
||||
File f(getMountPath(mountPath).c_str(), O_RDWR | O_CREAT | O_TRUNC);
|
||||
// Create a single file named "mounted" and write "mounted" into it.
|
||||
auto pathToNewFile = getPathToMountMarker(mountPath);
|
||||
File f(pathToNewFile, O_RDWR | O_CREAT | O_TRUNC);
|
||||
StringPiece data{"mounted"};
|
||||
folly::writeFull(f.fd(), data.data(), data.size());
|
||||
return f;
|
||||
}
|
||||
|
||||
void PrivHelperTestServer::fuseUnmount(const char* mountPath) {
|
||||
// Replace the file contents with "unmounted"
|
||||
File f(getMountPath(mountPath).c_str(), O_RDWR | O_CREAT | O_TRUNC);
|
||||
StringPiece data{"unmounted"};
|
||||
folly::writeFull(f.fd(), data.data(), data.size());
|
||||
}
|
||||
|
||||
std::string PrivHelperTestServer::getMountPath(StringPiece mountPath) const {
|
||||
return tmpDir_ + "/" + mountPath.str();
|
||||
// Replace the file contents with "unmounted".
|
||||
folly::writeFile(
|
||||
StringPiece{"unmounted"}, getPathToMountMarker(mountPath).c_str());
|
||||
}
|
||||
|
||||
bool PrivHelperTestServer::isMounted(folly::StringPiece mountPath) const {
|
||||
return checkIfMarkerFileHasContents(
|
||||
getPathToMountMarker(mountPath), "mounted");
|
||||
}
|
||||
|
||||
string PrivHelperTestServer::getPathToMountMarker(StringPiece mountPath) const {
|
||||
return mountPath.str() + "/mounted";
|
||||
}
|
||||
|
||||
// Bind mounts.
|
||||
|
||||
void PrivHelperTestServer::bindMount(
|
||||
const char* clientPath,
|
||||
const char* mountPath) {
|
||||
// Create a single file named "bind-mounted" and write "bind-mounted" into it.
|
||||
|
||||
// Normally, the caller to the PrivHelper (in practice, EdenServer) is
|
||||
// responsible for creating the directory before requesting the bind mount.
|
||||
boost::filesystem::create_directories(mountPath);
|
||||
|
||||
auto fileInMountPath = getPathToBindMountMarker(mountPath);
|
||||
folly::writeFile(StringPiece{"bind-mounted"}, fileInMountPath.c_str());
|
||||
}
|
||||
|
||||
void PrivHelperTestServer::bindUnmount(const char* mountPath) {
|
||||
// Replace the file contents with "bind-unmounted".
|
||||
folly::writeFile(
|
||||
StringPiece{"bind-unmounted"},
|
||||
getPathToBindMountMarker(mountPath).c_str());
|
||||
}
|
||||
|
||||
bool PrivHelperTestServer::isBindMounted(folly::StringPiece mountPath) const {
|
||||
return checkIfMarkerFileHasContents(
|
||||
getPathToBindMountMarker(mountPath), "bind-mounted");
|
||||
}
|
||||
|
||||
string PrivHelperTestServer::getPathToBindMountMarker(
|
||||
StringPiece mountPath) const {
|
||||
return mountPath.str() + "/bind-mounted";
|
||||
}
|
||||
|
||||
// General helpers.
|
||||
|
||||
bool PrivHelperTestServer::checkIfMarkerFileHasContents(
|
||||
const string pathToMarkerFile,
|
||||
const string contents) const {
|
||||
try {
|
||||
std::string data;
|
||||
folly::readFile(getMountPath(mountPath).c_str(), data, 256);
|
||||
return data == "mounted";
|
||||
string data;
|
||||
folly::readFile(pathToMarkerFile.c_str(), data, 256);
|
||||
return data == contents;
|
||||
} catch (const std::system_error& ex) {
|
||||
if (ex.code().category() == std::system_category() &&
|
||||
ex.code().value() == ENOENT) {
|
||||
|
@ -24,12 +24,7 @@ namespace fusell {
|
||||
*/
|
||||
class PrivHelperTestServer : public PrivHelperServer {
|
||||
public:
|
||||
explicit PrivHelperTestServer(folly::StringPiece tmpDir);
|
||||
|
||||
/*
|
||||
* Get the path to the test file representing the given mount point.
|
||||
*/
|
||||
std::string getMountPath(folly::StringPiece mountPath) const;
|
||||
PrivHelperTestServer();
|
||||
|
||||
/*
|
||||
* Check if the given mount point is mounted.
|
||||
@ -39,11 +34,24 @@ class PrivHelperTestServer : public PrivHelperServer {
|
||||
*/
|
||||
bool isMounted(folly::StringPiece mountPath) const;
|
||||
|
||||
/**
|
||||
* Check if the given path is bind mounted.
|
||||
*/
|
||||
bool isBindMounted(folly::StringPiece mountPath) const;
|
||||
|
||||
private:
|
||||
folly::File fuseMount(const char* mountPath) override;
|
||||
void fuseUnmount(const char* mountPath) override;
|
||||
std::string getPathToMountMarker(folly::StringPiece mountPath) const;
|
||||
|
||||
std::string tmpDir_;
|
||||
void bindMount(const char* clientPath, const char* mountPath) override;
|
||||
void bindUnmount(const char* mountPath) override;
|
||||
std::string getPathToBindMountMarker(folly::StringPiece mountPath) const;
|
||||
|
||||
/** @return true if the marker file exists with the specified contents. */
|
||||
bool checkIfMarkerFileHasContents(
|
||||
const std::string pathToMarkerFile,
|
||||
const std::string contents) const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
cpp_unittest(
|
||||
name = "test",
|
||||
srcs = glob(["*.cpp"]),
|
||||
headers = glob(["*.h"]),
|
||||
deps = [
|
||||
"@/eden/fuse/privhelper:privhelper",
|
||||
"@/folly/experimental:test_util",
|
||||
],
|
||||
name = 'test',
|
||||
srcs = glob(['*.cpp']),
|
||||
headers = glob(['*.h']),
|
||||
deps = [
|
||||
'@/eden/fuse/privhelper:privhelper',
|
||||
'@/folly/experimental:test_util',
|
||||
],
|
||||
external_deps = [
|
||||
('boost', 'any'),
|
||||
],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user