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 @@ -1052,6 +1052,41 @@ return NumRead; } +std::error_code lockFile(int FD, std::chrono::milliseconds Timeout) { + struct flock Lock; + memset(&Lock, 0, sizeof(Lock)); + Lock.l_type = F_WRLCK; + Lock.l_whence = SEEK_SET; + Lock.l_start = 0; + Lock.l_len = 0; + unsigned T = Timeout.count(); + if (T == 0) + T = 1; + for (unsigned Counter = 0; Counter < T; ++Counter) { + if (::fcntl(FD, F_SETLK, &Lock) == -1) { + int Error = errno; + if (Error == EACCES || Error == EAGAIN) { + usleep(1000); + continue; + } + return std::error_code(Error, std::generic_category()); + } + return std::error_code(); + } + return std::error_code(EACCES, std::generic_category()); +} + +std::error_code unlockFile(int FD) { + struct flock Lock; + Lock.l_type = F_UNLCK; + Lock.l_whence = SEEK_SET; + Lock.l_start = 0; + Lock.l_len = 0; + if (::fcntl(FD, F_SETLK, &Lock) == -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,38 @@ 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); + unsigned T = Timeout.count(); + if (T == 0) + T = 1; + for (unsigned Counter = 0; Counter < T; ++Counter) { + 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); + } + } + } + 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,30 @@ } #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, 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); +} +#endif + } // anonymous namespace