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 @@ -1174,6 +1174,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 @@ -30,12 +30,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 @@ -458,7 +460,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. @@ -494,6 +496,23 @@ /// - from The Zen of Python, by Tim Peters /// void clear_error() { EC = std::error_code(); } + + /// Tries to lock the file. + /// + /// @returns RAII object that releases the lock upon leaving the scope. + /// + /// Possible use of this function is as follows: + /// + /// @code{.cpp} + /// if (auto L = stream.tryToLock()) { + /// // ... do action that require file to be locked. + /// } else { + /// handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) { + /// // ... handle lock error. + /// }); + /// } + /// @endcode + Expected tryToLock(); }; /// This returns a reference to a raw_ostream for standard output. Use it like: 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 @@ -858,6 +858,13 @@ return sys::Process::FileDescriptorHasColors(FD); } +Expected raw_fd_ostream::tryToLock() { + std::error_code EC = sys::fs::tryLockFile(FD); + 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 @@ -2150,4 +2150,46 @@ #endif } +TEST_F(FileSystemTest, FileLocker) { + 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.tryToLock()) { + 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.tryToLock(); + ASSERT_THAT_EXPECTED(L1, Succeeded()); + raw_fd_ostream Stream2(FD, false); + Expected L2 = Stream2.tryToLock(); + ASSERT_THAT_EXPECTED(L2, Failed()); + ASSERT_NO_ERROR(L1->unlock()); + Expected L3 = Stream.tryToLock(); + ASSERT_THAT_EXPECTED(L3, Succeeded()); + } + + ASSERT_NO_ERROR(fs::tryLockFile(FD)); + ASSERT_NO_ERROR(fs::unlockFile(FD)); +} + } // anonymous namespace