sapling/eden/fs/inodes/EdenMount.h
Chad Austin 008497c69a remove SerializedFileHandleMap
Summary: SerializedFileHandleMap is dead code now.

Reviewed By: strager

Differential Revision: D13381629

fbshipit-source-id: ba872aaf8335d2be68d6af0465bd04e4ca59d578
2018-12-13 12:29:13 -08:00

778 lines
23 KiB
C++

/*
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#pragma once
#include <folly/Portability.h>
#include <folly/SharedMutex.h>
#include <folly/Synchronized.h>
#include <folly/ThreadLocal.h>
#include <folly/futures/Future.h>
#include <folly/futures/Promise.h>
#include <folly/logging/Logger.h>
#include <chrono>
#include <memory>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include "eden/fs/fuse/EdenStats.h"
#include "eden/fs/fuse/FuseChannel.h"
#include "eden/fs/inodes/InodePtrFwd.h"
#include "eden/fs/inodes/OverlayFileAccess.h"
#include "eden/fs/journal/Journal.h"
#include "eden/fs/model/ParentCommits.h"
#include "eden/fs/service/gen-cpp2/eden_types.h"
#include "eden/fs/store/BlobAccess.h"
#include "eden/fs/takeover/TakeoverData.h"
#include "eden/fs/utils/PathFuncs.h"
namespace folly {
class EventBase;
class File;
template <typename T>
class Future;
} // namespace folly
namespace facebook {
namespace eden {
class BindMount;
class BlobCache;
class CheckoutConflict;
class ClientConfig;
class Clock;
class DiffContext;
class EdenDispatcher;
class FuseChannel;
class InodeDiffCallback;
class InodeMap;
class MountPoint;
struct InodeMetadata;
template <typename T>
class InodeTable;
using InodeMetadataTable = InodeTable<InodeMetadata>;
class ObjectStore;
class Overlay;
class OverlayFileAccess;
class ServerState;
class Tree;
class UnboundedQueueExecutor;
class RenameLock;
class SharedRenameLock;
/**
* Represents types of keys for some fb303 counters.
*/
enum class CounterName {
/**
* Represents count of loaded inodes in the current mount.
*/
LOADED,
/**
* Represents count of unloaded inodes in the current mount.
*/
UNLOADED
};
/**
* Contains the uid and gid of the owner of the files in the mount
*/
struct Owner {
uid_t uid;
gid_t gid;
};
/**
* EdenMount contains all of the data about a specific eden mount point.
*
* This contains:
* - The MountPoint object which manages our FUSE interactions with the kernel.
* - The ObjectStore object used for retreiving/storing object data.
* - The Overlay object used for storing local changes (that have not been
* committed/snapshotted yet).
*/
class EdenMount {
public:
/**
* Create a shared_ptr to an EdenMount.
*
* Create an EdenMount instance Using an EdenMountDeleter.
* The caller must call initialize() after creating the EdenMount
* instance. This is not done implicitly because the graceful
* restart code needs to take the opportunity to update the InodeMap
* prior to the logic in initialize() running.
*/
static std::shared_ptr<EdenMount> create(
std::unique_ptr<ClientConfig> config,
std::shared_ptr<ObjectStore> objectStore,
std::shared_ptr<BlobCache> blobCache,
std::shared_ptr<ServerState> serverState);
/**
* Asynchronous EdenMount initialization - post instantiation.
*
* If takeover data is specified, it is used to initialize the inode map.
*/
FOLLY_NODISCARD folly::Future<folly::Unit> initialize(
const std::optional<SerializedInodeMap>& takeover = std::nullopt);
/**
* Destroy the EdenMount.
*
* This method generally does not need to be invoked directly, and will
* instead be invoked automatically by the shared_ptr<EdenMount> returned by
* create(), once it becomes unreferenced.
*
* If the EdenMount has not already been explicitly shutdown(), destroy()
* will trigger the shutdown(). destroy() blocks until the shutdown is
* complete, so it is advisable for callers to callers to explicitly trigger
* shutdown() themselves if they want to ensure that the shared_ptr
* destruction will not block on this operation.
*/
void destroy();
/**
* Shutdown the EdenMount.
*
* This should be called *after* the FUSE mount point has been unmounted from
* the kernel.
*
* This cleans up the in-memory data associated with the EdenMount, and waits
* for all outstanding InodeBase objects to become unreferenced and be
* destroyed.
*
* If doTakeover is true, this function will return populated
* SerializedFileHandleMap and SerializedInodeMap instances generated by
* calling FileHandleMap::serializeMap() and InodeMap::shutdown.
*
* If doTakeover is false, this function will return default-constructed
* SerializedFileHandleMap and SerializedInodeMap instances.
*/
folly::Future<SerializedInodeMap> shutdown(
bool doTakeover,
bool allowFuseNotStarted = false);
/**
* Get the FUSE channel for this mount point.
*
* This should only be called after the mount point has been successfully
* started. (It is the caller's responsibility to perform proper
* synchronization here with the mount start operation. This method provides
* no internal synchronization of its own.)
*/
FuseChannel* getFuseChannel() const;
/**
* Return the path to the mount point.
*/
const AbsolutePath& getPath() const;
/**
* Get the commit IDs of the working directory's parent commit(s).
*/
ParentCommits getParentCommits() const {
return parentInfo_.rlock()->parents;
}
/*
* Return bind mounts that are applied for this mount. These are based on the
* state of the ClientConfig when this EdenMount was created.
*/
const std::vector<BindMount>& getBindMounts() const;
/**
* Return the ObjectStore used by this mount point.
*
* The ObjectStore is guaranteed to be valid for the lifetime of the
* EdenMount.
*/
ObjectStore* getObjectStore() const {
return objectStore_.get();
}
/**
* Return Eden's blob cache.
*
* It is guaranteed to be valid for the lifetime of the EdenMount.
*/
BlobCache* getBlobCache() const {
return blobCache_.get();
}
/**
* Return the BlobAccess used by this mount point.
*
* The BlobAccess is guaranteed to be valid for the lifetime of the EdenMount.
*/
BlobAccess* getBlobAccess() {
return &blobAccess_;
}
/**
* Return the EdenDispatcher used for this mount.
*/
EdenDispatcher* getDispatcher() const {
return dispatcher_.get();
}
/**
* Return the InodeMap for this mount.
*/
InodeMap* getInodeMap() const {
return inodeMap_.get();
}
/**
* Return the Overlay for this mount.
*/
Overlay* getOverlay() const {
return overlay_.get();
}
OverlayFileAccess* getOverlayFileAccess() {
return &overlayFileAccess_;
}
InodeMetadataTable* getInodeMetadataTable() const;
Journal& getJournal() {
return journal_;
}
uint64_t getMountGeneration() const {
return mountGeneration_;
}
const ClientConfig* getConfig() const {
return config_.get();
}
/**
* Returns the server's thread pool.
*/
const std::shared_ptr<UnboundedQueueExecutor>& getThreadPool() const;
/**
* Returns the Clock with which this mount was configured.
*/
const Clock& getClock() const {
return *clock_;
}
/** Get the TreeInode for the root of the mount. */
TreeInodePtr getRootInode() const;
/**
* Get the inode number for the .eden dir. Returns an empty InodeNumber
* prior to the .eden directory being set up.
*/
InodeNumber getDotEdenInodeNumber() const;
/** Convenience method for getting the Tree for the root of the mount. */
std::shared_ptr<const Tree> getRootTree() const;
folly::Future<std::shared_ptr<const Tree>> getRootTreeFuture() const;
/**
* Look up the Inode object for the specified path.
*
* This may fail with an InodeError containing ENOENT if the path does not
* exist, or ENOTDIR if one of the intermediate components along the path is
* not a directory.
*
* This may also fail with other exceptions if something else goes wrong
* besides the path being invalid (for instance, an error loading data from
* the ObjectStore).
*/
folly::Future<InodePtr> getInode(RelativePathPiece path) const;
/**
* A blocking version of getInode().
*
* @return the InodeBase for the specified path or throws a std::system_error
* with ENOENT.
*
* TODO: We should switch all callers to use the Future-base API, and remove
* the blocking API.
*/
InodePtr getInodeBlocking(RelativePathPiece path) const;
/**
* Syntactic sugar for getInode().get().asTreePtr()
*
* TODO: We should switch all callers to use the Future-base API, and remove
* the blocking API.
*/
TreeInodePtr getTreeInodeBlocking(RelativePathPiece path) const;
/**
* Syntactic sugar for getInode().get().asFilePtr()
*
* TODO: We should switch all callers to use the Future-base API, and remove
* the blocking API.
*/
FileInodePtr getFileInodeBlocking(RelativePathPiece path) const;
/**
* Chases (to bounded depth) and returns the final non-symlink in the
* (possibly 0-length) chain of symlinks rooted at pInode. Specifically:
* If pInode is a file or directory, it is immediately returned.
* If pInode is a symlink, the chain rooted at it chased down until
* one of the following conditions:
* 1) an entity outside this mount is encountered => error (EXDEV);
* 2) an non-symlink item under this mount is found => this item is returned;
* 3) a maximum depth is exceeded => error (ELOOP).
* 4) absolute path entity is encountered => error (EPERM).
* 5) the input inode refers to an unlinked inode => error (ENOENT).
* 6) a symlink points to a non-existing entity => error (ENOENT)
* NOTE: a loop in the chain is handled by max depth length logic.
*/
folly::Future<InodePtr> resolveSymlink(InodePtr pInode) const;
/**
* Check out the specified commit.
*/
folly::Future<std::vector<CheckoutConflict>> checkout(
Hash snapshotHash,
CheckoutMode checkoutMode = CheckoutMode::NORMAL);
/**
* Chown the repository to the given uid and gid
*/
folly::Future<folly::Unit> chown(uid_t uid, gid_t gid);
/**
* This version of diff is primarily intended for testing.
* Use diff(InodeDiffCallback* callback, bool listIgnored) instead.
* The caller must ensure that the DiffContext object ctsPtr points to
* exists at least until the returned Future completes.
*/
folly::Future<folly::Unit> diff(const DiffContext* ctxPtr, Hash commitHash)
const;
/**
* Compute differences between the current commit and the working directory
* state.
*
* @param callback This callback will be invoked as differences are found.
* Note that the callback methods may be invoked simultaneously from
* multiple different threads, and the callback is responsible for
* performing synchronization (if it is needed).
* @param listIgnored Whether or not to inform the callback of ignored files.
* When listIgnored is set to false can speed up the diff computation, as
* the code does not need to descend into ignored directories at all.
*
* @return Returns a folly::Future that will be fulfilled when the diff
* operation is complete. This is marked FOLLY_NODISCARD to
* make sure callers do not forget to wait for the operation to complete.
*/
FOLLY_NODISCARD folly::Future<folly::Unit> diff(
InodeDiffCallback* callback,
Hash commitHash,
bool listIgnored = false) const;
/**
* Reset the state to point to the specified parent commit(s), without
* modifying the working directory contents at all.
*/
void resetParents(const ParentCommits& parents);
/**
* Reset the state to point to the specified parent commit, without
* modifying the working directory contents at all.
*
* This is a small wrapper around resetParents() for when the code knows at
* compile time that it will only ever have a single parent commit on this
* code path.
*/
void resetParent(const Hash& parent);
/**
* Acquire the rename lock in exclusive mode.
*/
RenameLock acquireRenameLock();
/**
* Acquire the rename lock in shared mode.
*/
SharedRenameLock acquireSharedRenameLock();
/**
* Returns a pointer to a stats instance associated with this mountpoint.
* Today this is the global stats instance, but in the future it will be
* a mount point specific instance.
*/
ThreadLocalEdenStats* getStats() const;
folly::Logger& getStraceLogger() {
return straceLogger_;
}
/**
* Returns the last checkout time in the Eden mount.
*/
struct timespec getLastCheckoutTime() const;
/**
* Set the last checkout time.
*
* This is intended primarily for use in test code.
*/
void setLastCheckoutTime(std::chrono::system_clock::time_point time);
/**
* Returns the key value to an fb303 counter.
*/
std::string getCounterName(CounterName name);
struct ParentInfo {
ParentCommits parents;
};
/**
* Mounts the filesystem in the VFS and spawns worker threads to
* dispatch the fuse session.
*
* Returns a Future that will complete as soon as the filesystem has been
* successfully mounted, or as soon as the mount fails (state transitions
* to RUNNING or FUSE_ERROR).
*/
FOLLY_NODISCARD folly::Future<folly::Unit> startFuse();
/**
* Take over a FUSE channel for an existing mount point.
*
* This spins up worker threads to service the existing FUSE channel and
* returns immediately, or throws an exception on error.
*/
void takeoverFuse(FuseChannelData takeoverData);
/**
* Obtains a future that will complete once the fuse channel has wound down.
*/
FOLLY_NODISCARD folly::Future<TakeoverData::MountInfo>
getFuseCompletionFuture();
Owner getOwner() const {
return *owner_.rlock();
}
void setOwner(uid_t uid, gid_t gid) {
auto owner = owner_.wlock();
owner->uid = uid;
owner->gid = gid;
}
/**
* Return a new stat structure that has been minimally initialized with
* data for this mount point.
*
* The caller must still initialize all file-specific data (inode number,
* file mode, size, timestamps, link count, etc).
*/
struct stat initStatData() const;
/**
* Given a mode_t, return an initial InodeMetadata. All timestamps are set
* to the last checkout time and uid and gid are set to the creator of the
* mount.
*/
struct InodeMetadata getInitialInodeMetadata(mode_t mode) const;
/**
* mount any configured bind mounts.
* This requires that the filesystem already be mounted, and must not
* be called in the context of a fuseWorkerThread().
*/
FOLLY_NODISCARD folly::Future<folly::Unit> performBindMounts();
/**
* Ensures the path `fromRoot` is a directory. If it is not, then it creates
* subdirectories until it is. If creating a subdirectory fails, it throws an
* exception.
*/
FOLLY_NODISCARD folly::Future<folly::Unit> ensureDirectoryExists(
RelativePathPiece fromRoot);
private:
friend class RenameLock;
friend class SharedRenameLock;
class JournalDiffCallback;
/**
* The current running state of the EdenMount.
*
* For now this primarily tracks the status of the shutdown process.
* In the future we may want to add other states to also track the status of
* the actual mount point in the kernel. (e.g., a "STARTING" state before
* RUNNING for when the kernel mount point has not been fully set up yet, and
* an "UNMOUNTING" state if we have requested the kernel to unmount the mount
* point and that has not completed yet. UNMOUNTING would occur between
* RUNNING and SHUT_DOWN.) One possible downside of tracking
* STARTING/UNMOUNTING is that not every EdenMount object actually has a FUSE
* mount. During unit tests we create EdenMount objects without ever
* actually mounting them in the kernel.
*/
enum class State : uint32_t {
/**
* Freshly created.
*/
UNINITIALIZED,
/**
* Starting to mount fuse.
*/
STARTING,
/**
* The EdenMount is running normally.
*/
RUNNING,
/**
* Encountered an error while starting fuse mount.
*/
FUSE_ERROR,
/**
* EdenMount::shutdown() has been called, but it is not complete yet.
*/
SHUTTING_DOWN,
/**
* EdenMount::shutdown() has completed, but there are still outstanding
* references so EdenMount::destroy() has not been called yet.
*
* When EdenMount::destroy() is called the object can be destroyed
* immediately.
*/
SHUT_DOWN,
/**
* EdenMount::destroy() has been called, but the shutdown is not complete
* yet. There are no remaining references to the EdenMount at this point,
* so when the shutdown completes it will be automatically destroyed.
*/
DESTROYING
};
/**
* Recursive method used for resolveSymlink() implementation
*/
folly::Future<InodePtr>
resolveSymlinkImpl(InodePtr pInode, RelativePath&& path, size_t depth) const;
/**
* Attempt to transition from expected -> newState.
* If the current state is expected then the state is set to newState
* and returns boolean.
* Otherwise the current state is left untouched and returns false.
*/
bool doStateTransition(State expected, State newState);
EdenMount(
std::unique_ptr<ClientConfig> config,
std::shared_ptr<ObjectStore> objectStore,
std::shared_ptr<BlobCache> blobCache,
std::shared_ptr<ServerState> serverState);
// Forbidden copy constructor and assignment operator
EdenMount(EdenMount const&) = delete;
EdenMount& operator=(EdenMount const&) = delete;
folly::Future<TreeInodePtr> createRootInode(
const ParentCommits& parentCommits);
FOLLY_NODISCARD folly::Future<folly::Unit> setupDotEden(TreeInodePtr root);
folly::Future<SerializedInodeMap> shutdownImpl(bool doTakeover);
std::unique_ptr<DiffContext> createDiffContext(
InodeDiffCallback* callback,
bool listIgnored) const;
/**
* Construct the channel_ member variable.
*/
void createFuseChannel(folly::File fuseDevice);
/**
* Once the FuseChannel has been initialized, set up callbacks to clean up
* correctly when the channel shuts down.
*/
void fuseInitSuccessful(FuseChannel::StopFuture&& fuseCompleteFuture);
/**
* Private destructor.
*
* This should not be invoked by callers directly. Use the destroy() method
* above (or the EdenMountDeleter if you plan to store the EdenMount in a
* std::unique_ptr or std::shared_ptr).
*/
~EdenMount();
static constexpr int kMaxSymlinkChainDepth = 40; // max depth of symlink chain
const std::unique_ptr<const ClientConfig> config_;
/**
* A promise associated with the future returned from
* EdenMount::getFuseCompletionFuture() that completes when the
* fuseChannel has no work remaining and can be torn down.
* The future yields the underlying fuseDevice descriptor; it can
* be passed on during graceful restart or simply closed if we're
* unmounting and shutting down completely. In the unmount scenario
* the device should be closed prior to calling EdenMount::shutdown()
* so that the subsequent privilegedFuseUnmount() call won't block
* waiting on us for a response.
*/
folly::Promise<TakeoverData::MountInfo> fuseCompletionPromise_;
/**
* Eden server state shared across multiple mount points.
*/
std::shared_ptr<ServerState> serverState_;
std::unique_ptr<InodeMap> inodeMap_;
std::unique_ptr<EdenDispatcher> dispatcher_;
std::shared_ptr<ObjectStore> objectStore_;
std::shared_ptr<BlobCache> blobCache_;
BlobAccess blobAccess_;
std::unique_ptr<Overlay> overlay_;
OverlayFileAccess overlayFileAccess_;
InodeNumber dotEdenInodeNumber_{};
/**
* A mutex around all name-changing operations in this mount point.
*
* This includes rename() operations as well as unlink() and rmdir().
* Any operation that modifies an existing InodeBase's location_ data must
* hold the rename lock.
*/
folly::SharedMutex renameMutex_;
/**
* The IDs of the parent commit(s) of the working directory.
*
* In most circumstances there will only be a single parent, but there
* will be two parents when in the middle of resolving a merge conflict.
*/
folly::Synchronized<ParentInfo> parentInfo_;
/*
* Note that this config will not be updated if the user modifies the
* underlying config files after the ClientConfig was created.
*/
const std::vector<BindMount> bindMounts_;
Journal journal_;
/**
* A number to uniquely identify this particular incarnation of this mount.
* We use bits from the process id and the time at which we were mounted.
*/
const uint64_t mountGeneration_;
/**
* The path to the unix socket that can be used to address us via thrift
*/
AbsolutePath socketPath_;
/**
* A log category for logging strace-events for this mount point.
*
* All FUSE operations to this mount point will get logged to this category.
* The category name is of the following form: "eden.strace.<mount_path>"
*/
folly::Logger straceLogger_;
/**
* The timestamp of the last time that a checkout operation was performed in
* this mount. This is used to initialize the timestamps of newly loaded
* inodes. (Since the file contents might have logically been update by the
* checkout operation.)
*
* We store this as a struct timespec rather than a std::chrono::time_point
* since this is primarily used by FUSE APIs which need a timespec.
*
* This is managed with its own Synchronized lock separate from other state
* since it needs to be accessed when constructing inodes. This is a very
* low level lock in our lock ordering hierarchy: No other locks should be
* acquired while holding this lock.
*/
folly::Synchronized<struct timespec> lastCheckoutTime_;
/**
* The current state of the mount point.
*/
std::atomic<State> state_{State::UNINITIALIZED};
/**
* uid and gid that we'll set as the owners in the stat information
* returned via initStatData().
*/
folly::Synchronized<Owner> owner_;
/**
* The associated fuse channel to the kernel.
*/
std::unique_ptr<FuseChannel, FuseChannelDeleter> channel_;
/**
* The clock. This is also available as serverState_->getClock().
* We still keep it as a separate member variable for now so that getClock()
* can be inline without having to include ServerState.h in this file.
*/
std::shared_ptr<Clock> clock_;
};
/**
* RenameLock is a holder for an EdenMount's rename mutex.
*
* This is primarily useful so it can be forward declared easily,
* but it also provides a helper method to ensure that it is currently holding
* a lock on the desired mount.
*/
class RenameLock : public std::unique_lock<folly::SharedMutex> {
public:
RenameLock() {}
explicit RenameLock(EdenMount* mount)
: std::unique_lock<folly::SharedMutex>{mount->renameMutex_} {}
bool isHeld(EdenMount* mount) const {
return owns_lock() && (mutex() == &mount->renameMutex_);
}
};
/**
* SharedRenameLock is a holder for an EdenMount's rename mutex in shared mode.
*/
class SharedRenameLock : public std::shared_lock<folly::SharedMutex> {
public:
explicit SharedRenameLock(EdenMount* mount)
: std::shared_lock<folly::SharedMutex>{mount->renameMutex_} {}
bool isHeld(EdenMount* mount) const {
return owns_lock() && (mutex() == &mount->renameMutex_);
}
};
/**
* EdenMountDeleter acts as a deleter argument for std::shared_ptr or
* std::unique_ptr.
*/
class EdenMountDeleter {
public:
void operator()(EdenMount* mount) {
mount->destroy();
}
};
} // namespace eden
} // namespace facebook