/* * 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 "eden/fs/inodes/EdenMount.h" #include "eden/fs/inodes/InodeNumber.h" #include "eden/fs/inodes/InodePtr.h" #include "eden/fs/inodes/ServerState.h" #include "eden/fs/inodes/overlay/gen-cpp2/overlay_types.h" #include "eden/fs/model/TreeEntry.h" #include "eden/fs/testharness/FakeClock.h" #include "eden/fs/utils/PathFuncs.h" namespace folly { template class Future; struct Unit; class ManualExecutor; } // namespace folly namespace facebook { namespace eden { class BlobCache; class TreeCache; class CheckoutConfig; class FakeBackingStore; class FakeFuse; class FakePrivHelper; class FakeTreeBuilder; class FileInode; class LocalStore; class TreeInode; template class StoredObject; using StoredHash = StoredObject; struct TestMountFile { RelativePath path; std::string contents; uint8_t rwx = 0b110; TreeEntryType type = TreeEntryType::REGULAR_FILE; /** Performs a structural equals comparison. */ bool operator==(const TestMountFile& other) const; /** * @param p is a StringPiece (rather than a RelativePath) for convenience * for creating instances of TestMountFile for unit tests. */ TestMountFile(folly::StringPiece p, folly::StringPiece c) : path(p), contents(c.str()) {} }; class TestMount { public: /** * Create a new uninitialized TestMount. * * The TestMount will not be fully initialized yet. The caller must * populate the object store as desired, and then call initialize() to * create the underlying EdenMount object once the commit has been set up. */ TestMount(); /** * Create a new TestMount * * If startReady is true, all of the Tree and Blob objects created by the * rootBuilder will be made immediately ready in the FakeBackingStore. If * startReady is false the objects will not be ready, and attempts to * retrieve them from the backing store will not complete until the caller * explicitly marks them ready. * * However, the root Tree object is always marked ready. This is necessary * to create the EdenMount object. * * If an initialCommitHash is not explicitly specified, makeTestHash("1") * will be used. */ explicit TestMount(FakeTreeBuilder& rootBuilder, bool startReady = true); explicit TestMount(FakeTreeBuilder&& rootBuilder); TestMount( const RootId& initialCommitHash, FakeTreeBuilder& rootBuilder, bool startReady = true); ~TestMount(); /** * Initialize the mount. * * This should only be used if the TestMount was default-constructed. * The caller must have already defined the root commit. The lastCheckoutTime * is read from the FakeClock. */ void initialize(const RootId& initialCommitHash) { initialize(initialCommitHash, getClock().getTimePoint()); } /** * Initialize the mount. * * This should only be used if the TestMount was default-constructed. * The caller must have already defined the root commit. */ void initialize( const RootId& initialCommitHash, std::chrono::system_clock::time_point lastCheckoutTime); /** * Initialize the mount. * * This should only be used if the TestMount was default-constructed. * The caller must have already defined the root Tree in the object store. */ void initialize(const RootId& initialCommitHash, Hash rootTreeHash); /** * Initialize the mount from the given root tree. * * This should only be used if the TestMount was default-constructed. * * If an initialCommitHash is not explicitly specified, makeTestHash("1") * will be used. */ void initialize( const RootId& initialCommitHash, FakeTreeBuilder& rootBuilder, bool startReady = true); void initialize(FakeTreeBuilder& rootBuilder, bool startReady = true); /** * Like initialize, except EdenMount::initialize is not called. * * This should only be used if the TestMount was default-constructed. */ void createMountWithoutInitializing( const RootId& initialCommitHash, FakeTreeBuilder& rootBuilder, bool startReady = true); void createMountWithoutInitializing( FakeTreeBuilder& rootBuilder, bool startReady = true); /** * Perform FUSE initialization on the EdenMount. * * This function calls registerFakeFuse on your behalf. * * Preconditions: * - initialize() was called. */ void startFuseAndWait(std::shared_ptr); /** * Get the CheckoutConfig object. * * The CheckoutConfig object provides methods to get the paths to the mount * point, the client directory, etc. */ CheckoutConfig* getConfig() const { return config_.get(); } /** * Callers can use this to populate the LocalStore before calling build(). */ const std::shared_ptr& getLocalStore() const { return localStore_; } /** * Callers can use this to populate the BackingStore before calling build(). */ const std::shared_ptr& getBackingStore() const { return backingStore_; } const std::shared_ptr& getBlobCache() const { return blobCache_; } const std::shared_ptr& getTreeCache() const { return treeCache_; } #ifndef _WIN32 FuseDispatcher* getDispatcher() const; #endif // !_WIN32 /** * Access to the TestMount's FakeClock which is referenced by the underlying * EdenMount (and thus inodes). */ FakeClock& getClock() { return *clock_; } /** * Re-create the EdenMount object, simulating a scenario where it was * unmounted and then remounted. * * Note that if the caller is holding references to the old EdenMount object * this will prevent it from being destroyed. This may result in an error * trying to create the new EdenMount if the old mount object still exists * and is still holding a lock on the overlay or other data structures. */ void remount(); #ifndef _WIN32 /** * Simulate an edenfs daemon takeover for this mount. */ void remountGracefully(); #endif /** * Add file to the mount; it will be available in the overlay. */ void addFile(folly::StringPiece path, folly::StringPiece contents); void mkdir(folly::StringPiece path); /** Overwrites the contents of an existing file. */ void overwriteFile(folly::StringPiece path, folly::StringPiece contents); /** Does the equivalent of mv(1). */ void move(folly::StringPiece src, folly::StringPiece dest); std::string readFile(folly::StringPiece path); /** Returns true if path identifies a regular file in the tree. */ bool hasFileAt(folly::StringPiece path); void deleteFile(folly::StringPiece path); void rmdir(folly::StringPiece path); #ifndef _WIN32 /** * Create symlink named path pointing to pointsTo or throw exception if fail */ void addSymlink(folly::StringPiece path, folly::StringPiece pointsTo); void chmod(folly::StringPiece path, mode_t permissions); #endif InodePtr getInode(RelativePathPiece path) const; InodePtr getInode(folly::StringPiece path) const; TreeInodePtr getTreeInode(RelativePathPiece path) const; TreeInodePtr getTreeInode(folly::StringPiece path) const; FileInodePtr getFileInode(RelativePathPiece path) const; FileInodePtr getFileInode(folly::StringPiece path) const; /** * Walk the entire tree and load all inode objects. */ void loadAllInodes(); FOLLY_NODISCARD folly::Future loadAllInodesFuture(); /** * Load all inodes [recursively] under the specified subdirectory. */ static void loadAllInodes(const TreeInodePtr& treeInode); FOLLY_NODISCARD static folly::Future loadAllInodesFuture( const TreeInodePtr& treeInode); /** Convenience method for getting the Tree for the root of the mount. */ std::shared_ptr getRootTree() 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. */ std::string loadFileContentsFromPath(std::string path); std::shared_ptr& getEdenMount() & noexcept { return edenMount_; } const std::shared_ptr& getEdenMount() const& { return edenMount_; } #ifndef _WIN32 const std::shared_ptr& getPrivHelper() const { return privHelper_; } #endif // !_WIN32 void registerFakeFuse(std::shared_ptr fuse); const std::shared_ptr& getServerState() const { return serverState_; } /** * Get a hash to use for the next commit. * * This mostly just helps pick easily readable commit IDs that increment * over the course of a test. * * This returns "0000000000000000000000000000000000000001" on the first call, * "0000000000000000000000000000000000000002" on the second, etc. */ RootId nextCommitHash(); /** * Helper function to create a commit from a FakeTreeBuilder and call * EdenMount::resetCommit() with the result. */ void resetCommit(FakeTreeBuilder& builder, bool setReady); void resetCommit( const RootId& commitHash, FakeTreeBuilder& builder, bool setReady); /** * Returns true if the overlay contains a file for this inode. */ bool hasOverlayData(InodeNumber inodeNumber) const; /** * Returns true if the inode metadata table has an entry for this inode. */ bool hasMetadata(InodeNumber inodeNumber) const; /** * Returns number of functions executed. */ size_t drainServerExecutor(); std::shared_ptr getServerExecutor() const { return serverExecutor_; } private: void createMount(); void initTestDirectory(); void setInitialCommit(const RootId& commitHash); void setInitialCommit(const RootId& commitHash, Hash rootTreeHash); /** * Initialize the Eden mount. This is an internal function to initialize and * start the EdenMount. */ void initializeEdenMount(); /** * The temporary directory for this TestMount. * * This must be stored as a member variable to ensure the temporary directory * lives for the duration of the test. * * We intentionally list it before the edenMount_ so it gets constructed * first, and destroyed (and deleted from disk) after the EdenMount is * destroyed. */ std::optional testDir_; std::shared_ptr edenMount_; #ifndef _WIN32 std::unique_ptr dispatcher_; #endif std::shared_ptr localStore_; std::shared_ptr backingStore_; std::shared_ptr stats_; std::shared_ptr blobCache_; std::shared_ptr treeCache_; std::shared_ptr edenConfig_; /* * config_ is only set before edenMount_ has been initialized. * When edenMount_ is created we pass ownership of the config to edenMount_. */ std::unique_ptr config_; /** * A counter for creating temporary commit hashes via the nextCommitHash() * function. * * This is atomic just in case, but in general I would expect most tests to * perform all TestMount manipulation from a single thread. */ std::atomic commitNumber_{1}; std::shared_ptr clock_ = std::make_shared(); std::shared_ptr privHelper_; // The ManualExecutor must be destroyed prior to the EdenMount. Otherwise, // when clearing its queue, it will deallocate functions with captured values // that still reference the EdenMount (or its owned objects). std::shared_ptr serverExecutor_; std::shared_ptr serverState_; }; } // namespace eden } // namespace facebook