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,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. +public: + FileLocker(int FD) : FD(FD) {} + FileLocker(const FileLocker &L) = delete; + FileLocker(FileLocker &&L) : FD(L.FD) { L.FD = -1; } + ~FileLocker() { + if (FD != -1) + sys::fs::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 = sys::fs::unlockFile(FD); + FD = -1; + return Result; + } + return std::error_code(); + } +}; + +/// Tries to lock file represented by its descriptor. +/// +/// Possible use of this function is as follows: +/// +/// @code{.cpp} +/// if (auto L = fs::getLock(FD)) { +/// // ... do action that require file to be locked. +/// } else { +/// handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) { +/// // ... handle lock error. +/// }); +/// } +/// @endcode +inline Expected getLock(int FD, std::chrono::milliseconds T) { + std::error_code EC = tryLockFile(FD, T); + if (!EC) + return FileLocker(FD); + return errorCodeToError(EC); +} + +/// Lock file represented by its descriptor. +/// +/// It acts as \ref getLock(int,std::chrono::milliseconds) but waits +/// infinitely. +inline Expected getLock(int FD) { + std::error_code EC = lockFile(FD); + if (!EC) + return FileLocker(FD); + return errorCodeToError(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,9 +513,9 @@ // raw_fd_ostream //===----------------------------------------------------------------------===// -static int getFD(StringRef Filename, std::error_code &EC, - sys::fs::CreationDisposition Disp, sys::fs::FileAccess Access, - sys::fs::OpenFlags Flags) { +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) && "Cannot make a raw_ostream from a read-only descriptor!"); @@ -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 @@ -2124,4 +2124,57 @@ #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(Stream.getFD()); + ASSERT_NO_ERROR(EC); + EC = fs::unlockFile(Stream.getFD()); + ASSERT_NO_ERROR(EC); + + if (Expected L = fs::getLock(FD, std::chrono::seconds(1))) { + ASSERT_ERROR(fs::tryLockFile(Stream.getFD())); + ASSERT_NO_ERROR(L->unlock()); + ASSERT_NO_ERROR(fs::tryLockFile(Stream.getFD())); + ASSERT_NO_ERROR(fs::unlockFile(Stream.getFD())); + } else { + EXPECT_THAT_EXPECTED(L, Failed()); + handleAllErrors(std::move(L.takeError()), [&](ErrorInfoBase &EIB) {}); + } + + ASSERT_NO_ERROR(fs::tryLockFile(Stream.getFD())); + ASSERT_NO_ERROR(fs::unlockFile(Stream.getFD())); + + { + Expected L1 = fs::getLock(FD, std::chrono::seconds(1)); + ASSERT_THAT_EXPECTED(L1, Succeeded()); + Expected L2 = + fs::getLock(Stream.getFD(), std::chrono::seconds(1)); + ASSERT_THAT_EXPECTED(L2, Failed()); + ASSERT_NO_ERROR(L1->unlock()); + + Expected L3 = + fs::getLock(Stream.getFD(), std::chrono::seconds(1)); + ASSERT_THAT_EXPECTED(L3, Succeeded()); + } + + ASSERT_NO_ERROR(fs::tryLockFile(Stream.getFD())); + ASSERT_NO_ERROR(fs::unlockFile(Stream.getFD())); + + // Lock using blocking method. + { + Expected L1 = fs::getLock(FD); + ASSERT_THAT_EXPECTED(L1, Succeeded()); + } + + ASSERT_NO_ERROR(fs::tryLockFile(Stream.getFD())); + ASSERT_NO_ERROR(fs::unlockFile(Stream.getFD())); +} + } // anonymous namespace