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 @@ -1164,6 +1164,64 @@ /// 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. + std::error_code EC; ///< Result of lock operation. + +public: + FileLocker(int FD, + std::chrono::milliseconds T = std::chrono::milliseconds(1000)) + : FD(FD) { + EC = sys::fs::tryLockFile(FD, T); + } + + FileLocker(FileLocker &&L) : FD(L.FD), EC(L.EC) { + L.EC = make_error_code(std::errc::no_lock_available); + } + + ~FileLocker() { + // Do not unlock files that were not locked. + if (!EC) + sys::fs::unlockFile(FD); + } + + std::error_code error() const { return EC; } + bool locked() const { return !EC; } + + std::error_code unlock() { + if (!EC) { + std::error_code Err = sys::fs::unlockFile(FD); + // If unlock is successful, set error code to imitate unlocked object. + if (!Err) + EC = make_error_code(std::errc::no_lock_available); + return Err; + } + return EC; + } +}; + +/// Tries to lock file represented by its descriptor. +/// +/// Possible use of this function is as follows: +/// +/// @code{.cpp} +/// if (Expected L = fs::tryLock(FD)) { +/// // ... do action that require file to be locked. +/// } else { +/// handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) { +/// // ... handle lock error. +/// }); +/// } +/// @endcode +inline Expected +tryLock(int FD, std::chrono::milliseconds T = std::chrono::milliseconds(1000)) { + FileLocker Lock(FD, T); + if (Lock.locked()) + return std::move(Lock); + return errorCodeToError(Lock.error()); +} + 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 @@ -458,7 +458,9 @@ /// fsync. void close(); - bool supportsSeeking() { return SupportsSeeking; } + int getFD() const { return FD; } + + 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. 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 @@ -513,7 +513,7 @@ // raw_fd_ostream //===----------------------------------------------------------------------===// -static int getFD(StringRef Filename, std::error_code &EC, +static int openFile(StringRef Filename, std::error_code &EC, sys::fs::CreationDisposition Disp, sys::fs::FileAccess Access, sys::fs::OpenFlags Flags) { assert((Access & sys::fs::FA_Write) && @@ -563,7 +563,7 @@ sys::fs::CreationDisposition Disp, sys::fs::FileAccess Access, sys::fs::OpenFlags Flags) - : raw_fd_ostream(getFD(Filename, EC, Disp, Access, Flags), true) {} + : raw_fd_ostream(openFile(Filename, EC, Disp, Access, Flags), true) {} /// FD is the file descriptor that this writes to. If ShouldClose is true, this /// closes the file when the stream is destroyed. 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 @@ -2004,4 +2004,95 @@ ASSERT_NO_ERROR(EC); } +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(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + + if (Expected L = fs::tryLock(FD)) { + ASSERT_TRUE(L->locked()); + ASSERT_NO_ERROR(L->error()); + + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_ERROR(EC); + + EC = L->unlock(); + ASSERT_NO_ERROR(EC); + ASSERT_ERROR(L->error()); + ASSERT_FALSE(L->locked()); + + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + } else { + ADD_FAILURE(); + handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) {}); + } + + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + + if (Expected L1 = fs::tryLock(FD)) { + ASSERT_TRUE(L1->locked()); + ASSERT_NO_ERROR(L1->error()); + + if (Expected L2 = fs::tryLock(Stream.getFD())) { + ADD_FAILURE(); + } else { + handleAllErrors(std::move(L2.takeError()), [&](ErrorInfoBase &EIB) {}); + } + + EC = L1->unlock(); + ASSERT_NO_ERROR(EC); + ASSERT_FALSE(L1->locked()); + ASSERT_ERROR(L1->error()); + + if (Expected L2 = fs::tryLock(Stream.getFD())) { + ASSERT_TRUE(L2->locked()); + ASSERT_NO_ERROR(L2->error()); + } else { + handleAllErrors(std::move(L2.takeError()), [&](ErrorInfoBase &EIB) {}); + ADD_FAILURE(); + } + } + + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + + if (Expected L = fs::tryLock(FD)) { + ASSERT_TRUE(L->locked()); + ASSERT_NO_ERROR(L->error()); + + EC = L->unlock(); + ASSERT_NO_ERROR(EC); + ASSERT_ERROR(L->error()); + + EC = L->unlock(); + ASSERT_ERROR(EC); + ASSERT_ERROR(L->error()); + } else { + handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) {}); + ADD_FAILURE(); + } + + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); +} + } // anonymous namespace