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 @@ -1131,6 +1131,28 @@ openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None, SmallVectorImpl *RealPath = nullptr); +/// Locks the file. +/// +/// The function locks the entire file for exclusive access by the calling +/// process. +/// +/// @param File The descriptor representing the file to lock. +/// @param Timeout Time in milliseconds that the process should wait before +/// reporting lock failure. Zero value means try to get lock only +/// once. +/// @returns errc::success if lock is successfully obtained, +/// errc::no_lock_available if the file cannot be locked, or platform-specific +/// error_code otherwise. +std::error_code tryLockFile(int FD, + std::chrono::milliseconds Timeout = std::chrono::milliseconds(0)); + +/// Unlock the file. +/// +/// @param File The descriptor representing the file to unlock. +/// @returns errc::success if lock is successfully released or platform-specific +/// error_code otherwise. +std::error_code unlockFile(int FD); + /// @brief Close the file object. This should be used instead of ::close for /// portability. On error, the caller should assume the file is closed, as is /// the case for Process::SafelyCloseFileDescriptor diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc --- a/llvm/lib/Support/Unix/Path.inc +++ b/llvm/lib/Support/Unix/Path.inc @@ -31,6 +31,7 @@ #include #endif +#include #include #include @@ -1052,6 +1053,29 @@ return NumRead; } +std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { + auto Start = std::chrono::steady_clock::now(); + auto End = Start + Timeout; + do { + if (::flock(FD, LOCK_EX | LOCK_NB) == -1) { + int Error = errno; + if (Error == EWOULDBLOCK) { + usleep(1000); + continue; + } + return std::error_code(Error, std::generic_category()); + } + return std::error_code(); + } while (std::chrono::steady_clock::now() < End); + return make_error_code(errc::no_lock_available); +} + +std::error_code unlockFile(int FD) { + if (::flock(FD, LOCK_UN) == -1) + return std::error_code(errno, std::generic_category()); + return std::error_code(); +} + std::error_code closeFile(file_t &F) { file_t TmpF = F; F = kInvalidFile; diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc --- a/llvm/lib/Support/Windows/Path.inc +++ b/llvm/lib/Support/Windows/Path.inc @@ -1260,6 +1260,37 @@ return readNativeFileImpl(FileHandle, Buf, &Overlapped); } +std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { + DWORD Flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY; + OVERLAPPED OV = {0}; + file_t File = convertFDToNativeFile(FD); + auto Start = std::chrono::steady_clock::now(); + auto End = Start + Timeout; + do { + if (::LockFileEx(File, Flags, 0, MAXDWORD, MAXDWORD, &OV)) { + return std::error_code(); + } else { + DWORD Error = ::GetLastError(); + if (Error == ERROR_LOCK_VIOLATION) { + ::Sleep(1); + continue; + } else { + return mapWindowsError(Error); + } + } + } while (std::chrono::steady_clock::now() < End); + return mapWindowsError(ERROR_LOCK_VIOLATION); +} + +std::error_code unlockFile(int FD) { + OVERLAPPED OV = { 0 }; + file_t File = convertFDToNativeFile(FD); + if (::UnlockFileEx(File, 0, MAXDWORD, MAXDWORD, &OV)) + return std::error_code(); + else + return mapWindowsError(::GetLastError()); +} + std::error_code closeFile(file_t &F) { file_t TmpF = F; F = kInvalidFile; 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 @@ -1934,4 +1934,26 @@ } #endif +TEST_F(FileSystemTest, lockFile) { + int FD1, FD2; + std::error_code EC; + SmallString<64> TempPath; + EC = fs::createTemporaryFile("test", "temp", FD1, TempPath); + ASSERT_NO_ERROR(EC); + FileRemover Cleanup(TempPath); + EC = fs::openFileForReadWrite(TempPath, FD2, + fs::CD_OpenExisting, fs::OF_Append); + ASSERT_NO_ERROR(EC); + EC = fs::tryLockFile(FD1); + ASSERT_NO_ERROR(EC); + EC = fs::tryLockFile(FD2, std::chrono::milliseconds(5)); + ASSERT_EQ(errc::no_lock_available, EC); + EC = fs::unlockFile(FD1); + ASSERT_NO_ERROR(EC); + EC = fs::tryLockFile(FD2); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(FD2); + ASSERT_NO_ERROR(EC); +} + } // anonymous namespace