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,39 @@ /// 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() { + // 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; + } +}; + 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 @@ -2006,6 +2006,77 @@ EC = fs::unlockFile(FD2); 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); + + { + fs::FileLocker L(FD); + ASSERT_TRUE(L.locked()); + ASSERT_NO_ERROR(L.error()); + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_EQ(errc::no_lock_available, EC); + ASSERT_TRUE(L.locked()); + ASSERT_NO_ERROR(L.error()); + } + + EC = fs::tryLockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + + { + fs::FileLocker L(FD); + 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); + } + + { + fs::FileLocker L(FD); + ASSERT_TRUE(L.locked()); + ASSERT_NO_ERROR(L.error()); + { + fs::FileLocker L2(Stream.getFD(), std::chrono::milliseconds(5)); + ASSERT_FALSE(L2.locked()); + ASSERT_ERROR(L2.error()); + EC = L2.unlock(); + ASSERT_ERROR(EC); + ASSERT_FALSE(L2.locked()); + ASSERT_ERROR(L2.error()); + } + } + + { + fs::FileLocker L(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()); + } +} #endif } // anonymous namespace