diff --git a/llvm/include/llvm/Support/FileSystem.h b/llvm/include/llvm/Support/FileSystem.h --- a/llvm/include/llvm/Support/FileSystem.h +++ b/llvm/include/llvm/Support/FileSystem.h @@ -1179,6 +1179,35 @@ /// means that the filesystem may have failed to perform some buffered writes. std::error_code closeFile(file_t &F); +/// RAII class that facilitates file locking. +class FileLocker { + int FD; ///< Locked file handle. + FileLocker(int FD) : FD(FD) {} + friend class llvm::raw_fd_ostream; + +public: + FileLocker(const FileLocker &L) = delete; + FileLocker(FileLocker &&L) : FD(L.FD) { L.FD = -1; } + ~FileLocker() { + if (FD != -1) + unlockFile(FD); + } + FileLocker &operator=(FileLocker &&L) { + FD = L.FD; + L.FD = -1; + return *this; + } + FileLocker &operator=(const FileLocker &L) = delete; + std::error_code unlock() { + if (FD != -1) { + std::error_code Result = unlockFile(FD); + FD = -1; + return Result; + } + return std::error_code(); + } +}; + std::error_code getUniqueID(const Twine Path, UniqueID &Result); /// Get disk space usage information. diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h --- a/llvm/include/llvm/Support/raw_ostream.h +++ b/llvm/include/llvm/Support/raw_ostream.h @@ -16,6 +16,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include +#include #include #include #include @@ -30,12 +31,14 @@ class FormattedString; class FormattedNumber; class FormattedBytes; +template class LLVM_NODISCARD Expected; namespace sys { namespace fs { enum FileAccess : unsigned; enum OpenFlags : unsigned; enum CreationDisposition : unsigned; +class FileLocker; } // end namespace fs } // end namespace sys @@ -468,7 +471,7 @@ /// fsync. void close(); - bool supportsSeeking() { return SupportsSeeking; } + bool supportsSeeking() const { return SupportsSeeking; } /// Flushes the stream and repositions the underlying file descriptor position /// to the offset specified from the beginning of the file. @@ -496,6 +499,38 @@ /// - from The Zen of Python, by Tim Peters /// void clear_error() { EC = std::error_code(); } + + /// Locks the underlying file. + /// + /// @returns RAII object that releases the lock upon leaving the scope, if the + /// locking was successful. Otherwise returns corresponding + /// error code. + /// + /// The function blocks the current thread until the lock become available or + /// error occurs. + /// + /// Possible use of this function may be as follows: + /// + /// @code{.cpp} + /// if (auto L = stream.lock()) { + /// // ... do action that require file to be locked. + /// } else { + /// handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) { + /// // ... handle lock error. + /// }); + /// } + /// @endcode + LLVM_NODISCARD Expected lock(); + + /// Tries to lock the underlying file within the specified period. + /// + /// @returns RAII object that releases the lock upon leaving the scope, if the + /// locking was successful. Otherwise returns corresponding + /// error code. + /// + /// It is used as @ref lock. + LLVM_NODISCARD + Expected tryLockFor(std::chrono::milliseconds Timeout); }; /// This returns a reference to a raw_fd_ostream for standard output. Use it diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp --- a/llvm/lib/Support/raw_ostream.cpp +++ b/llvm/lib/Support/raw_ostream.cpp @@ -861,6 +861,21 @@ return sys::Process::FileDescriptorHasColors(FD); } +Expected raw_fd_ostream::lock() { + std::error_code EC = sys::fs::lockFile(FD); + if (!EC) + return sys::fs::FileLocker(FD); + return errorCodeToError(EC); +} + +Expected +raw_fd_ostream::tryLockFor(std::chrono::milliseconds Timeout) { + std::error_code EC = sys::fs::tryLockFile(FD, Timeout); + if (!EC) + return sys::fs::FileLocker(FD); + return errorCodeToError(EC); +} + void raw_fd_ostream::anchor() {} //===----------------------------------------------------------------------===// diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp --- a/llvm/unittests/Support/Path.cpp +++ b/llvm/unittests/Support/Path.cpp @@ -2179,4 +2179,51 @@ } #endif +#ifdef _WIN32 +// Windows refuses lock request if file region is already locked by the same +// process. POSIX system in this case updates the existing lock. +TEST_F(FileSystemTest, FileLocker) { + using namespace std::chrono; + int FD; + std::error_code EC; + SmallString<64> TempPath; + EC = fs::createTemporaryFile("test", "temp", FD, TempPath); + ASSERT_NO_ERROR(EC); + FileRemover Cleanup(TempPath); + raw_fd_ostream Stream(TempPath, EC); + + EC = fs::tryLockFile(FD); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(FD); + ASSERT_NO_ERROR(EC); + + if (auto L = Stream.lock()) { + ASSERT_ERROR(fs::tryLockFile(FD)); + ASSERT_NO_ERROR(L->unlock()); + ASSERT_NO_ERROR(fs::tryLockFile(FD)); + ASSERT_NO_ERROR(fs::unlockFile(FD)); + } else { + ADD_FAILURE(); + handleAllErrors(L.takeError(), [&](ErrorInfoBase &EIB) {}); + } + + ASSERT_NO_ERROR(fs::tryLockFile(FD)); + ASSERT_NO_ERROR(fs::unlockFile(FD)); + + { + Expected L1 = Stream.lock(); + ASSERT_THAT_EXPECTED(L1, Succeeded()); + raw_fd_ostream Stream2(FD, false); + Expected L2 = Stream2.tryLockFor(250ms); + ASSERT_THAT_EXPECTED(L2, Failed()); + ASSERT_NO_ERROR(L1->unlock()); + Expected L3 = Stream.tryLockFor(0ms); + ASSERT_THAT_EXPECTED(L3, Succeeded()); + } + + ASSERT_NO_ERROR(fs::tryLockFile(FD)); + ASSERT_NO_ERROR(fs::unlockFile(FD)); +} +#endif + } // anonymous namespace