/* * Copyright (c) Facebook, Inc. and its affiliates. * * This software may be used and distributed according to the terms of the * GNU General Public License version 2. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "eden/fs/inodes/CacheHint.h" #include "eden/fs/inodes/InodePtrFwd.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/utils/PathFuncs.h" #ifndef _WIN32 #include "eden/fs/fuse/Dispatcher.h" #include "eden/fs/fuse/FuseChannel.h" #include "eden/fs/inodes/OverlayFileAccess.h" #include "eden/fs/takeover/TakeoverData.h" #else #include "eden/fs/inodes/win/DirList.h" // @manual #include "eden/fs/win/mount/FsChannel.h" // @manual #include "eden/fs/win/store/WinStore.h" // @manual #include "eden/fs/win/utils/Stub.h" // @manual #endif DECLARE_string(edenfsctlPath); namespace apache { namespace thrift { class ResponseChannelRequest; } } // namespace apache namespace folly { class EventBase; class File; template class Future; } // namespace folly namespace facebook { namespace eden { class BindMount; class BlobCache; class CheckoutConfig; class CheckoutConflict; class Clock; class DiffContext; class EdenDispatcher; class FuseChannel; class FuseDeviceUnmountedDuringInitialization; class DiffCallback; class InodeMap; class MountPoint; struct InodeMetadata; template class InodeTable; using InodeMetadataTable = InodeTable; class ObjectStore; class Overlay; class OverlayFileAccess; class ServerState; class Tree; class TreePrefetchLease; 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. */ INODEMAP_LOADED, /** * Represents count of unloaded inodes in the current mount. */ INODEMAP_UNLOADED, /** * Represents the amount of memory used by deltas in the change log */ JOURNAL_MEMORY, /** * Represents the number of entries in the change log */ JOURNAL_ENTRIES, /** * Represents the duration of the journal in seconds end to end */ JOURNAL_DURATION, /** * Represents the maximum deltas iterated over in the Journal's forEachDelta */ JOURNAL_MAX_FILES_ACCUMULATED }; /** * Contains the uid and gid of the owner of the files in the mount */ struct Owner { uid_t uid; gid_t gid; }; /** * Durations of the various stages of checkout. */ struct CheckoutTimes { using duration = std::chrono::steady_clock::duration; duration didAcquireParentsLock{}; duration didLookupTrees{}; duration didDiff{}; duration didAcquireRenameLock{}; duration didCheckout{}; duration didFinish{}; }; struct CheckoutResult { std::vector conflicts; CheckoutTimes times; }; /** * 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: using State = MountState; /** * Create a shared_ptr to an EdenMount. * * The caller must call initialize() after creating the EdenMount to load data * required to access the mount's inodes. No inode-related methods may be * called on the EdenMount until initialize() has successfully completed. */ static std::shared_ptr create( std::unique_ptr config, std::shared_ptr objectStore, std::shared_ptr blobCache, std::shared_ptr serverState, std::unique_ptr journal); /** * Asynchronous EdenMount initialization - post instantiation. * * If takeover data is specified, it is used to initialize the inode map. */ #ifndef _WIN32 FOLLY_NODISCARD folly::Future initialize( const std::optional& takeover = std::nullopt); #else FOLLY_NODISCARD folly::Future initialize( std::unique_ptr&& fsChannel); #endif /** * Destroy the EdenMount. * * This method generally does not need to be invoked directly, and will * instead be invoked automatically by the shared_ptr 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* calling unmount() (i.e. 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::SemiFuture shutdown( bool doTakeover, bool allowFuseNotStarted = false); /** * Call the umount(2) syscall to tell the kernel to remove this filesystem. * * After umount(2) succeeds, the following operations happen independently and * concurrently: * * * The future returned by unmount() is fulfilled successfully. * * The future returned by getFuseCompletionFuture() is fulfilled. * * If startFuse() is in progress, unmount() can cancel startFuse(). * * If startFuse() is in progress, unmount() might wait for startFuse() to * finish before calling umount(2). * * If neither startFuse() nor takeoverFuse() has been called, unmount() * finishes successfully without calling umount(2). Thereafter, startFuse() * and takeoverFuse() will both fail with an EdenMountCancelled exception. * * unmount() is idempotent: If unmount() has already been called, this * function immediately returns a Future which will complete at the same time * the original call to unmount() completes. */ FOLLY_NODISCARD folly::Future unmount(); /** * Get the current state of this mount. * * Note that the state may be changed by another thread immediately after this * method is called, so this method should primarily only be used for * debugging & diagnostics. */ State getState() const { return state_.load(std::memory_order_acquire); } /** * Check if inode operations can be performed on this EdenMount. * * This returns false for mounts that are still initializing and do not have * their root inode loaded yet. This also returns false for mounts that are * shutting down. */ bool isSafeForInodeAccess() const { auto state = getState(); return !( state == State::UNINITIALIZED || state == State::INITIALIZING || state == State::SHUTTING_DOWN); } /** * 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 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_; } #ifdef _WIN32 /** * Return the pointer to FsChannel on Windows */ FsChannel* getFsChannel() const { return fsChannel_.get(); } #else /** * Return the EdenDispatcher used for this mount. */ EdenDispatcher* getDispatcher() const { return dispatcher_.get(); } #endif // !_WIN32 /** * Return the InodeMap for this mount. */ InodeMap* getInodeMap() const { return inodeMap_.get(); } /** * Return the Overlay for this mount. */ Overlay* getOverlay() const { return overlay_.get(); } #ifndef _WIN32 OverlayFileAccess* getOverlayFileAccess() { return &overlayFileAccess_; } #endif // !_WIN32 InodeMetadataTable* getInodeMetadataTable() const; /** * Return the Journal used by this mount point. * * The Journal is guaranteed to be valid for the lifetime of the EdenMount. */ Journal& getJournal() { return *journal_; } uint64_t getMountGeneration() const { return mountGeneration_; } const CheckoutConfig* getConfig() const { return config_.get(); } /** * Returns the server's thread pool. */ const std::shared_ptr& 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; #ifndef _WIN32 /** * Get the inode number for the .eden dir. Returns an empty InodeNumber * prior to the .eden directory being set up. */ InodeNumber getDotEdenInodeNumber() const; #endif // !_WIN32 /** Convenience method for getting the Tree for the root of the mount. */ folly::Future> getRootTree() 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 getInode(RelativePathPiece path) const; /** * Resolves symlinks and loads file contents from the Inode at the given path. * This loads the entire file contents into memory, so this can be expensive * for large files. * * The fetchContext object must remain valid until the future is completed. */ folly::Future loadFileContentsFromPath( ObjectFetchContext& fetchContext, RelativePathPiece path, CacheHint cacheHint = CacheHint::LikelyNeededAgain) const; /** * Resolves symlinks and loads file contents. This loads the entire file * contents into memory, so this can be expensive for large files. * * The fetchContext object must remain valid until the future is completed. * * TODO: add maxSize parameter to cause the command to fail if the file is * over a certain size. */ folly::Future loadFileContents( ObjectFetchContext& fetchContext, InodePtr fileInodePtr, CacheHint cacheHint = CacheHint::LikelyNeededAgain) 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 resolveSymlink( ObjectFetchContext& fetchContext, InodePtr pInode, CacheHint cacheHint = CacheHint::LikelyNeededAgain) const; /** * Check out the specified commit. */ folly::Future checkout( Hash snapshotHash, CheckoutMode checkoutMode = CheckoutMode::NORMAL); /** * Chown the repository to the given uid and gid */ folly::Future chown(uid_t uid, gid_t gid); /** * Compute differences between the current commit and the working directory * state. * * @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. * @param enforceCurrentParent Whether or not to return an error if the * specified commitHash does not match the actual current working * directory parent. If this is false the code will still compute a diff * against the specified commitHash even the working directory parent * points elsewhere, or when a checkout is currently in progress. * @param request This ResposeChannelRequest is passed from the ServiceHandler * and is used to check if the request is still active, because if the * request is no longer active we will cancel this diff operation. * * @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> diff( Hash commitHash, bool listIgnored = false, bool enforceCurrentParent = true, apache::thrift::ResponseChannelRequest* FOLLY_NULLABLE request = nullptr); /** * This version of diff is primarily intended for testing. * Use diff(DiffCallback* callback, bool listIgnored) instead. * The caller must ensure that the DiffContext object ctsPtr points to * exists at least until the returned Future completes. */ FOLLY_NODISCARD folly::Future diff( DiffContext* ctxPtr, Hash commitHash) 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. */ EdenStats* getStats() const; folly::Logger& getStraceLogger() { return straceLogger_; } const std::shared_ptr& getServerState() const { return serverState_; } /** * 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). * * If unmount() is called before startFuse() is called, then startFuse() * does the following: * * * startFuse() does not attempt to mount the filesystem * * The returned Future is fulfilled with an EdenMountCancelled exception * * If unmount() is called while startFuse() is in progress, then startFuse() * does the following: * * * The filesystem is unmounted (if it was mounted) * * The returned Future is fulfilled with an * FuseDeviceUnmountedDuringInitialization exception */ FOLLY_NODISCARD folly::Future startFuse(bool readOnly); /** * 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. * * If unmount() is called before takeoverFuse() is called, then takeoverFuse() * throws an EdenMountCancelled exception. */ void takeoverFuse(FuseChannelData takeoverData); /** * Obtains a future that will complete once the fuse channel has wound down. * * This method may be called at any time, but the returned future will only be * fulfilled if startFuse() completes successfully. If startFuse() fails or * is never called, the future returned by getFuseCompletionFuture() will * never complete. */ FOLLY_NODISCARD folly::Future 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::SemiFuture performBindMounts(); FOLLY_NODISCARD folly::Future addBindMount( RelativePathPiece repoPath, AbsolutePathPiece targetPath); FOLLY_NODISCARD folly::Future removeBindMount( RelativePathPiece repoPath); /** * 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 ensureDirectoryExists( RelativePathPiece fromRoot); /** * Request to start a new tree prefetch. * * Returns a new TreePrefetchLease if you can start a new prefetch, or * std::nullopt if there are too many prefetches already in progress and a new * one should not be started. If a TreePrefetchLease object is returned the * caller should hold onto it until the prefetch is complete. When the * TreePrefetchLease is destroyed this will inform the EdenMount that the * prefetch has finished. */ FOLLY_NODISCARD std::optional tryStartTreePrefetch( TreeInodePtr treeInode); #ifdef _WIN32 /** * The following functions are to start and stop Eden Mount on Windows. They * setup and destroy the ProjectedFS channel. */ void start(); void stop(); /** * The following functions are to fetch and set information in the Inode * Tree. These are used by the ProjectedFS and TestMount for testing. */ // TODO(puneetk): The following function should be updated to future based, so // that we can executed them asynchronously. This would help to implement // ProjectedFs Asynchronous Callback Handling: // (https://docs.microsoft.com/en-us/windows/win32/projfs/asynchronous-callback-handling). /** * fetchFileInfo is mainly used by the ProjectedFS to fetch the file metadata. * The size field contains the size from the backing repo and not the current * file size. If the file is materialized then ProjectedFS will have the * current file size. */ FOLLY_NODISCARD bool fetchFileInfo( const RelativePathPiece path, FileMetadata& metadata); /* * readFile returns the file contents of the file. It gets the contents of the * file from the backing store if it's not materialized. If the file is * materialized, It will read the FS interface to fetch the file contents from * ProjectedFs cache. */ FOLLY_NODISCARD std::string readFile(const RelativePathPiece path); /** * enumerateDirectory will fetch the directory entries for the given path. * Same as fetchFileInfo, the file size here will be the size from backing * repo and not the current file size. */ FOLLY_NODISCARD DirList enumerateDirectory(const RelativePathPiece path); void enumerateDirectory( const RelativePathPiece path, std::vector& list); /* * createFile function is to create a the file or directory in the InodeTree. * This gets called in response of a new file or directory getting created by * user on the FS. This will never be called for a file which exists in the * backing repo. */ void createFile(const RelativePathPiece path, bool isDirectory); /* * materializeFile is called to report that the file contents are modified. * This is called by ProjectedFS when a file is closed after modification. */ void materializeFile(const RelativePathPiece path); /* * removeFile is to remove a file or directory from the inode tree. */ void removeFile(const RelativePathPiece path, bool isDirectory); /** * renameFile will rename a file or directory in the inode tree. * * This functions is called by ProjectedFS and TestMount to rename a file. */ void renameFile( const RelativePathPiece oldpath, const RelativePathPiece newpath); #endif private: friend class RenameLock; friend class SharedRenameLock; class JournalDiffCallback; /** * Recursive method used for resolveSymlink() implementation */ folly::Future resolveSymlinkImpl( ObjectFetchContext& fetchContext, InodePtr pInode, RelativePath&& path, size_t depth, CacheHint cacheHint) 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. */ FOLLY_NODISCARD bool tryToTransitionState(State expected, State newState); /** * Transition from expected -> newState. * * Throws an error if the current state does not match the expected state. */ void transitionState(State expected, State newState); /** * Transition from the STARTING state to the FUSE_ERROR state. * * Preconditions: * - `getState()` is STARTING or DESTROYING or SHUTTING_DOWN or SHUT_DOWN. * * Postconditions: * - If `getState()` was STARTING, `getState()` is now FUSE_ERROR. * - If `getState()` was not STARTING, `getState()` is unchanged. */ void transitionToFuseInitializationErrorState(); EdenMount( std::unique_ptr config, std::shared_ptr objectStore, std::shared_ptr blobCache, std::shared_ptr serverState, std::unique_ptr journal); // Forbidden copy constructor and assignment operator EdenMount(EdenMount const&) = delete; EdenMount& operator=(EdenMount const&) = delete; folly::Future createRootInode( const ParentCommits& parentCommits); #ifndef _WIN32 FOLLY_NODISCARD folly::Future setupDotEden(TreeInodePtr root); #endif // !_WIN32 folly::SemiFuture shutdownImpl(bool doTakeover); /** * Create a DiffContext to be passed through the TreeInode diff codepath. This * will be used to record differences through the callback (in which * listIgnored determines if ignored files will be reported in the callback) * and houses the thrift request in order to check to see if the diff() should * be short circuited */ std::unique_ptr createDiffContext( DiffCallback* callback, bool listIgnored = false, apache::thrift::ResponseChannelRequest* FOLLY_NULLABLE request = nullptr) const; /** * This accepts a callback which 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). It will be packaged into a DiffContext * and passed through the TreeInode diff() codepath */ FOLLY_NODISCARD folly::Future diff( DiffCallback* callback, Hash commitHash, bool listIgnored, bool enforceCurrentParent, apache::thrift::ResponseChannelRequest* FOLLY_NULLABLE request) const; #ifndef _WIN32 /** * Open the FUSE device and mount it using the mount(2) syscall. */ folly::Future fuseMount(bool readOnly); /** * Signal to unmount() that fuseMount() or takeoverFuse() has started. * * beginMount() returns a reference to * *mountingUnmountingState_->fuseMountPromise. To signal that the fuseMount() * has completed, set the promise's value (or exception) without * mountingUnmountingState_'s lock held. * * If unmount() was called in the past, beginMount() throws * EdenMountCancelled. * * Preconditions: * - `beginMount()` has not been called before. */ FOLLY_NODISCARD folly::Promise& beginMount(); /** * 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); #endif /** * 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(); friend class TreePrefetchLease; void treePrefetchFinished() noexcept; static constexpr int kMaxSymlinkChainDepth = 40; // max depth of symlink chain const std::unique_ptr 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 fuseCompletionPromise_; /** * Eden server state shared across multiple mount points. */ std::shared_ptr serverState_; std::unique_ptr inodeMap_; #ifndef _WIN32 std::unique_ptr dispatcher_; #endif std::shared_ptr objectStore_; std::shared_ptr blobCache_; BlobAccess blobAccess_; std::shared_ptr overlay_; #ifndef _WIN32 OverlayFileAccess overlayFileAccess_; InodeNumber dotEdenInodeNumber_{}; #endif // !_WIN32 /** * 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_; std::unique_ptr 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." */ 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 lastCheckoutTime_; struct MountingUnmountingState { bool fuseMountStarted() const noexcept; bool unmountStarted() const noexcept; /** * Whether or not the mount(2) syscall has been called (via fuseMount). * * Use this promise to wait for fuseMount to finish. * * * Empty optional: fuseMount/mount(2) has not been called yet. * (startFuse/fuseMount can be called.) * * Unfulfilled: fuseMount is in progress. * * Fulfilled with Unit: fuseMount completed successfully (via startFuse), * or we took over the FUSE device from another process (via * takeoverFuse). (startFuse or takeoverFuse can still be in progress.) * * Fulfilled with error: fuseMount failed, or fuseMount was cancelled. * * The state of this variable might not reflect whether the file system is * mounted. For example, if this promise is fulfilled with Unit, then * umount(8) is called by another process, the file system will not be * mounted. */ std::optional> fuseMountPromise; /** * Whether or not unmount has been called. * * * Empty optional: unmount has not been called yet. (unmount can be * called.) * * Unfulfilled: unmount is in progress, either waiting for a concurrent * fuseMount to complete or waiting for fuseUnmount to complete. * * Fulfilled with Unit: unmount was called. fuseUnmount completed * successfully, or fuseMount was never called for this EdenMount. * * Fulfilled with error: unmount was called, but fuseUnmount failed. * * The state of this variable might not reflect whether the file system is * unmounted. */ std::optional> unmountPromise; }; folly::Synchronized mountingUnmountingState_; /** * The current state of the mount point. */ std::atomic state_{State::UNINITIALIZED}; /** * uid and gid that we'll set as the owners in the stat information * returned via initStatData(). */ folly::Synchronized owner_; /** * The number of tree prefetches in progress for this mount point. */ std::atomic numPrefetchesInProgress_{0}; #ifdef _WIN32 /** * This is the channel between ProjectedFS and rest of Eden. */ std::unique_ptr fsChannel_; #else /** * The associated fuse channel to the kernel. */ std::unique_ptr channel_; #endif // !_WIN32 /** * 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_; }; /** * 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 { public: RenameLock() {} explicit RenameLock(EdenMount* mount) : std::unique_lock{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 { public: explicit SharedRenameLock(EdenMount* mount) : std::shared_lock{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(); } }; class EdenMountCancelled : public std::runtime_error { public: explicit EdenMountCancelled(); }; } // namespace eden } // namespace facebook