sapling/eden/fs/privhelper/PrivHelperConn.cpp
Katie Mancini 039692d1f7 privhelper: set the fstype to fuse.edenfs
Summary:
We are teaching eden to expose itself as fuse.edenfs so that fuse.edenfs can
be added to the PRUNEFS option of updatedb to prevent it from crawling an
EdenFS repo.

There were a few references to the vfstype in code, which we have fixed. But
just in case there is some other reference we can't find we want to rollout this
change with a config option to make it easy to put back.

The privhelper does not read the eden config directly, so we need to pass the
config value over the socket. Sometimes tests talk to the privhelper so its
important that the new eden daemon can still talk to the old privhelper.
Unfortunatly, the old privhelper is gonna error if we try to pass an extra value
it's not expecting. We could rollout the changes to the privhelper and then
wait to teach eden to pass the extra vfs type. But that will be slow. Instead
we can retry failed requests without the extra arg. This will allow the new
eden to still work with the old privhelper.

Generally the privhelper and eden process are both updated at the same time
in production, so the request should succeed on the first attempt.

Reviewed By: jdelliot

Differential Revision: D55531178

fbshipit-source-id: 8e085e26b8ee94ae657a22f93e9fb0ae3fdc5a24
2024-04-16 20:58:52 -07:00

531 lines
16 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#ifndef _WIN32
#include "eden/fs/privhelper/PrivHelperConn.h"
#include <fcntl.h>
#include <folly/Demangle.h>
#include <folly/Exception.h>
#include <folly/File.h>
#include <folly/FileUtil.h>
#include <folly/ScopeGuard.h>
#include <folly/SocketAddress.h>
#include <folly/futures/Future.h>
#include <folly/io/Cursor.h>
#include <folly/io/IOBuf.h>
#include <folly/logging/xlog.h>
#include <folly/portability/GFlags.h>
#include <folly/portability/Sockets.h>
#include <folly/portability/Unistd.h>
#include <sys/types.h>
#include "eden/common/utils/Bug.h"
#include "eden/common/utils/SystemError.h"
#include "eden/common/utils/Throw.h"
using folly::ByteRange;
using folly::checkUnixError;
using folly::IOBuf;
using folly::StringPiece;
using folly::io::Appender;
using folly::io::Cursor;
using std::string;
namespace facebook::eden {
namespace {
constexpr size_t kDefaultBufferSize = 1024;
// We need to bump this version number any time the protocol is changed. This is
// so that the EdenFS daemon and privhelper daemon understand which version of
// the protocol to use when sending/processing requests and responses.
constexpr uint32_t PRIVHELPER_CURRENT_VERSION = 1;
UnixSocket::Message serializeRequestPacket(
uint32_t xid,
PrivHelperConn::MsgType type) {
XLOGF(
DBG7,
"Serializing request packet with v{} protocol. Packet is {} bytes long.",
PRIVHELPER_CURRENT_VERSION,
sizeof(PrivHelperConn::PrivHelperPacket));
UnixSocket::Message msg;
msg.data = IOBuf(IOBuf::CREATE, kDefaultBufferSize);
Appender appender(&msg.data, kDefaultBufferSize);
appender.write<uint32_t>(PRIVHELPER_CURRENT_VERSION);
appender.write<uint32_t>(sizeof(PrivHelperConn::PrivHelperPacketMetadata));
appender.write<uint32_t>(xid);
appender.write<uint32_t>(static_cast<uint32_t>(type));
return msg;
}
void serializeString(Appender& a, StringPiece str) {
a.write<uint32_t>(str.size());
a.push(ByteRange(str));
}
std::string deserializeString(Cursor& cursor) {
const auto length = cursor.read<uint32_t>();
return cursor.readFixedString(length);
}
void serializeBool(Appender& a, bool b) {
a.write<uint8_t>(b);
}
bool deserializeBool(Cursor& cursor) {
return static_cast<bool>(cursor.read<uint8_t>());
}
void serializeUint16(Appender& a, uint16_t val) {
a.write<uint16_t>(val);
}
uint16_t deserializeUint16(Cursor& cursor) {
return cursor.read<uint16_t>();
}
void serializeUint32(Appender& a, uint64_t val) {
a.write<uint32_t>(val);
}
uint64_t deserializeUint32(Cursor& cursor) {
return cursor.read<uint32_t>();
}
void serializeSocketAddress(Appender& a, const folly::SocketAddress& addr) {
bool isInet = addr.isFamilyInet();
serializeBool(a, isInet);
if (isInet) {
serializeString(a, addr.getAddressStr());
serializeUint16(a, addr.getPort());
} else {
XCHECK_EQ(addr.getFamily(), AF_UNIX);
serializeString(a, addr.getPath());
}
}
folly::SocketAddress deserializeSocketAddress(Cursor& cursor) {
bool isInet = deserializeBool(cursor);
if (isInet) {
auto host = deserializeString(cursor);
auto port = deserializeUint16(cursor);
return folly::SocketAddress(host, port);
} else {
auto path = deserializeString(cursor);
return folly::SocketAddress::makeFromPath(path);
}
}
// Helper for setting close-on-exec. Not needed on systems
// that can atomically do this in socketpair
void setCloExecIfNoSockCloExec(int fd) {
#ifndef SOCK_CLOEXEC
auto flags = fcntl(fd, F_GETFD);
folly::checkPosixError(flags);
folly::checkPosixError(fcntl(fd, F_SETFD, flags | FD_CLOEXEC));
#else
(void)fd;
#endif
}
} // unnamed namespace
PrivHelperConn::PrivHelperPacket PrivHelperConn::parsePacket(Cursor& cursor) {
// read the size and version from the header
PrivHelperPacket packet{};
try {
packet.header = cursor.read<PrivHelperConn::PrivHelperPacketHeader>();
} catch (const std::out_of_range& e) {
throwf<std::runtime_error>(
"privhelper packet buffer did not include version/length header: {}",
e.what());
}
// read the packet metadata and record how many bytes are read
size_t pulledBytes = cursor.pullAtMost(
&packet.metadata,
std::min<size_t>(
packet.header.length,
sizeof(PrivHelperConn::PrivHelperPacketMetadata)));
XLOGF(
DBG7,
"We parsed a v{} packet for a total of {} bytes (header {} + metadata {})",
packet.header.version,
sizeof(PrivHelperPacketHeader) + pulledBytes,
sizeof(PrivHelperPacketHeader),
pulledBytes);
// We somehow read more bytes than the header indicated. This
// should be impossible and indicates a bug
assert(pulledBytes <= packet.header.length);
if (pulledBytes < packet.header.length) {
// We need to advance the cursor since the received packet is larger
// than we expected it would be
uint32_t sizeDifference = packet.header.length - pulledBytes;
XLOGF(
DBG7,
"Metadata is larger than expected ({} bytes). Pulled {} bytes, advancing the cursor by {} bytes.",
packet.header.length,
pulledBytes,
sizeDifference);
cursor.skip(sizeDifference);
}
return packet;
}
void PrivHelperConn::serializeResponsePacket(
PrivHelperConn::PrivHelperPacket& packet,
folly::io::RWPrivateCursor& cursor) {
XLOGF(
DBG7,
"Serializing response packet with v{} protocol. Packet is {} bytes long.",
PRIVHELPER_CURRENT_VERSION,
sizeof(packet));
cursor.write<uint32_t>(PRIVHELPER_CURRENT_VERSION);
cursor.write<uint32_t>(sizeof(packet.metadata));
cursor.write<uint32_t>(packet.metadata.transaction_id);
cursor.write<uint32_t>(packet.metadata.msg_type);
}
void PrivHelperConn::createConnPair(folly::File& client, folly::File& server) {
std::array<int, 2> sockpair;
checkUnixError(
socketpair(
AF_UNIX,
SOCK_STREAM
#ifdef SOCK_CLOEXEC
| SOCK_CLOEXEC
#endif
,
0,
sockpair.data()),
"failed to create socket pair for privhelper");
setCloExecIfNoSockCloExec(sockpair[0]);
setCloExecIfNoSockCloExec(sockpair[1]);
client = folly::File{sockpair[0], /*ownsFd=*/true};
server = folly::File{sockpair[1], /*ownsFd=*/true};
}
UnixSocket::Message PrivHelperConn::serializeMountRequest(
uint32_t xid,
StringPiece mountPoint,
bool readOnly,
std::optional<StringPiece> vfsType) {
auto msg = serializeRequestPacket(xid, REQ_MOUNT_FUSE);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPoint);
serializeBool(appender, readOnly);
if (vfsType.has_value()) {
serializeString(appender, vfsType.value());
}
return msg;
}
void PrivHelperConn::parseMountRequest(
Cursor& cursor,
string& mountPoint,
bool& readOnly,
string& vfsType) {
mountPoint = deserializeString(cursor);
readOnly = deserializeBool(cursor);
if (!cursor.isAtEnd()) {
vfsType = deserializeString(cursor);
} else {
vfsType = "fuse";
}
checkAtEnd(cursor, "mount request");
}
UnixSocket::Message PrivHelperConn::serializeMountNfsRequest(
uint32_t xid,
folly::StringPiece mountPoint,
folly::SocketAddress mountdAddr,
folly::SocketAddress nfsdAddr,
bool readOnly,
uint32_t iosize,
bool useReaddirplus) {
auto msg = serializeRequestPacket(xid, REQ_MOUNT_NFS);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPoint);
serializeSocketAddress(appender, mountdAddr);
serializeSocketAddress(appender, nfsdAddr);
serializeBool(appender, readOnly);
serializeUint32(appender, iosize);
serializeBool(appender, useReaddirplus);
return msg;
}
void PrivHelperConn::parseMountNfsRequest(
folly::io::Cursor& cursor,
std::string& mountPoint,
folly::SocketAddress& mountdAddr,
folly::SocketAddress& nfsdAddr,
bool& readOnly,
uint32_t& iosize,
bool& useReaddirplus) {
mountPoint = deserializeString(cursor);
mountdAddr = deserializeSocketAddress(cursor);
nfsdAddr = deserializeSocketAddress(cursor);
readOnly = deserializeBool(cursor);
iosize = deserializeUint32(cursor);
useReaddirplus = deserializeBool(cursor);
checkAtEnd(cursor, "mount nfs request");
}
UnixSocket::Message PrivHelperConn::serializeUnmountRequest(
uint32_t xid,
StringPiece mountPoint) {
auto msg = serializeRequestPacket(xid, REQ_UNMOUNT_FUSE);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPoint);
return msg;
}
void PrivHelperConn::parseUnmountRequest(Cursor& cursor, string& mountPoint) {
mountPoint = deserializeString(cursor);
checkAtEnd(cursor, "unmount request");
}
UnixSocket::Message PrivHelperConn::serializeNfsUnmountRequest(
uint32_t xid,
StringPiece mountPoint) {
auto msg = serializeRequestPacket(xid, REQ_UNMOUNT_NFS);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPoint);
return msg;
}
void PrivHelperConn::parseNfsUnmountRequest(
Cursor& cursor,
string& mountPoint) {
mountPoint = deserializeString(cursor);
checkAtEnd(cursor, "unmount request");
}
UnixSocket::Message PrivHelperConn::serializeTakeoverShutdownRequest(
uint32_t xid,
StringPiece mountPoint) {
auto msg = serializeRequestPacket(xid, REQ_TAKEOVER_SHUTDOWN);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPoint);
return msg;
}
void PrivHelperConn::parseTakeoverShutdownRequest(
Cursor& cursor,
string& mountPoint) {
mountPoint = deserializeString(cursor);
checkAtEnd(cursor, "takeover shutdown request");
}
UnixSocket::Message PrivHelperConn::serializeTakeoverStartupRequest(
uint32_t xid,
folly::StringPiece mountPoint,
const std::vector<std::string>& bindMounts) {
auto msg = serializeRequestPacket(xid, REQ_TAKEOVER_STARTUP);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPoint);
appender.write<uint32_t>(bindMounts.size());
for (const auto& path : bindMounts) {
serializeString(appender, path);
}
return msg;
}
void PrivHelperConn::parseTakeoverStartupRequest(
Cursor& cursor,
std::string& mountPoint,
std::vector<std::string>& bindMounts) {
mountPoint = deserializeString(cursor);
auto n = cursor.read<uint32_t>();
while (n-- != 0) {
bindMounts.push_back(deserializeString(cursor));
}
checkAtEnd(cursor, "takeover startup request");
}
void PrivHelperConn::parseEmptyResponse(
MsgType reqType,
const UnixSocket::Message& msg) {
Cursor cursor(&msg.data);
PrivHelperPacket packet = parsePacket(cursor);
// In the future, we may parse empty repsonses differently depending on the
// the version we get back from the parsed packet. For now, we'll parse all
// empty responses in the same way.
if (packet.metadata.msg_type == RESP_ERROR) {
rethrowErrorResponse(cursor);
} else if (packet.metadata.msg_type != reqType) {
throwf<std::runtime_error>(
"unexpected response type {} for request {} of type {} for version v{}",
packet.metadata.msg_type,
packet.metadata.transaction_id,
reqType,
packet.header.version);
}
}
UnixSocket::Message PrivHelperConn::serializeBindMountRequest(
uint32_t xid,
folly::StringPiece clientPath,
folly::StringPiece mountPath) {
auto msg = serializeRequestPacket(xid, REQ_MOUNT_BIND);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPath);
serializeString(appender, clientPath);
return msg;
}
void PrivHelperConn::parseBindMountRequest(
Cursor& cursor,
std::string& clientPath,
std::string& mountPath) {
mountPath = deserializeString(cursor);
clientPath = deserializeString(cursor);
checkAtEnd(cursor, "bind mount request");
}
UnixSocket::Message PrivHelperConn::serializeSetDaemonTimeoutRequest(
uint32_t xid,
std::chrono::nanoseconds duration) {
auto msg = serializeRequestPacket(xid, REQ_SET_DAEMON_TIMEOUT);
Appender appender(&msg.data, kDefaultBufferSize);
uint64_t durationNanoseconds = duration.count();
appender.write<uint64_t>(durationNanoseconds);
return msg;
}
void PrivHelperConn::parseSetDaemonTimeoutRequest(
Cursor& cursor,
std::chrono::nanoseconds& duration) {
duration = std::chrono::nanoseconds(cursor.read<uint64_t>());
checkAtEnd(cursor, "set daemon timeout request");
}
UnixSocket::Message PrivHelperConn::serializeSetUseEdenFsRequest(
uint32_t xid,
bool useEdenFs) {
auto msg = serializeRequestPacket(xid, REQ_SET_USE_EDENFS);
Appender appender(&msg.data, kDefaultBufferSize);
appender.write<uint64_t>(((useEdenFs) ? 1 : 0));
return msg;
}
void PrivHelperConn::parseSetUseEdenFsRequest(Cursor& cursor, bool& useEdenFs) {
useEdenFs = bool(cursor.read<uint64_t>());
checkAtEnd(cursor, "set use /dev/edenfs");
}
UnixSocket::Message PrivHelperConn::serializeBindUnMountRequest(
uint32_t xid,
folly::StringPiece mountPath) {
auto msg = serializeRequestPacket(xid, REQ_UNMOUNT_BIND);
Appender appender(&msg.data, kDefaultBufferSize);
serializeString(appender, mountPath);
return msg;
}
void PrivHelperConn::parseBindUnMountRequest(
Cursor& cursor,
std::string& mountPath) {
mountPath = deserializeString(cursor);
checkAtEnd(cursor, "bind mount request");
}
UnixSocket::Message PrivHelperConn::serializeSetLogFileRequest(
uint32_t xid,
folly::File logFile) {
auto msg = serializeRequestPacket(xid, REQ_SET_LOG_FILE);
msg.files.push_back(std::move(logFile));
return msg;
}
void PrivHelperConn::parseSetLogFileRequest(folly::io::Cursor& cursor) {
// REQ_SET_LOG_FILE has an empty body. The only contents
// are the file descriptor transferred with the request.
checkAtEnd(cursor, "set log file request");
}
void PrivHelperConn::serializeErrorResponse(
Appender& appender,
const std::exception& ex) {
int errnum = 0;
auto* sysEx = dynamic_cast<const std::system_error*>(&ex);
if (sysEx != nullptr && isErrnoError(*sysEx)) {
errnum = sysEx->code().value();
}
const auto exceptionType = folly::demangle(typeid(ex));
serializeErrorResponse(appender, ex.what(), errnum, exceptionType);
}
void PrivHelperConn::serializeErrorResponse(
Appender& appender,
folly::StringPiece message,
int errnum,
folly::StringPiece excType) {
appender.write<uint32_t>(errnum);
serializeString(appender, message);
serializeString(appender, excType);
}
[[noreturn]] void PrivHelperConn::rethrowErrorResponse(Cursor& cursor) {
const int errnum = cursor.read<uint32_t>();
const auto errmsg = deserializeString(cursor);
const auto excType = deserializeString(cursor);
if (errnum != 0) {
// If we have an errnum, rethrow the error as a std::system_error
//
// Unfortunately this will generally duplicate the errno message
// in the exception string. (errmsg already includes it from when the
// system_error was first thrown in the privhelper process, and the
// system_error constructor ends up including it again here.)
//
// There doesn't seem to be an easy way to avoid this at the moment,
// so for now we just live with it. (We could explicitly search for the
// error string at the end of errmsg and strip it off if found, but this
// seems more complicated than it's worth at the moment.)
throw std::system_error(errnum, std::generic_category(), errmsg);
}
throw PrivHelperError(excType, errmsg);
}
void PrivHelperConn::checkAtEnd(const Cursor& cursor, StringPiece messageType) {
if (!cursor.isAtEnd()) {
throw std::runtime_error(folly::to<string>(
"unexpected trailing data at end of ",
messageType,
": ",
cursor.totalLength(),
" bytes"));
}
}
PrivHelperError::PrivHelperError(StringPiece remoteExType, StringPiece msg)
: message_(folly::to<string>(remoteExType, ": ", msg)) {}
} // namespace facebook::eden
#endif