--- /dev/null
+// Copyright (C) 2024 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "filelock.hpp"
+
+#include <cerrno>
+#include <cstring>
+
+#ifdef _WIN32
+# include <io.h>
+# include <windows.h>
+#else
+# include <fcntl.h>
+# include <sys/file.h>
+# include <unistd.h>
+#endif
+
+namespace util {
+
+FileLock::FileLock(int fd) : m_fd(fd)
+{
+}
+
+FileLock::FileLock(FileLock&& other) noexcept
+ : m_fd(other.m_fd),
+ m_acquired(other.m_acquired)
+{
+ other.m_fd = -1;
+ other.m_acquired = false;
+}
+
+FileLock&
+FileLock::operator=(FileLock&& other) noexcept
+{
+ if (&other != this) {
+ m_fd = other.m_fd;
+ m_acquired = other.m_acquired;
+ other.m_fd = -1;
+ other.m_acquired = false;
+ }
+ return *this;
+}
+
+bool
+FileLock::acquire()
+{
+#ifdef _WIN32
+ HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(m_fd));
+ if (handle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+ m_acquired = LockFile(handle, 0, 0, MAXDWORD, MAXDWORD) != 0;
+#else
+ struct flock lock;
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ int result;
+ do {
+ result = fcntl(m_fd, F_SETLKW, &lock);
+ } while (result != 0 && errno == EINTR);
+ m_acquired = result == 0;
+#endif
+ return m_acquired;
+}
+
+void
+FileLock::release()
+{
+ if (!acquired()) {
+ return;
+ }
+
+#ifdef _WIN32
+ HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(m_fd));
+ if (handle != INVALID_HANDLE_VALUE) {
+ UnlockFile(handle, 0, 0, MAXDWORD, MAXDWORD);
+ }
+#else
+ struct flock lock;
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ int result;
+ do {
+ result = fcntl(m_fd, F_SETLK, &lock);
+ } while (result != 0 && errno == EINTR);
+#endif
+ m_acquired = false;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2024 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#pragma once
+
+#include <ccache/util/noncopyable.hpp>
+
+namespace util {
+
+class FileLock : util::NonCopyable
+{
+public:
+ explicit FileLock(int fd);
+ FileLock(FileLock&& other) noexcept;
+
+ FileLock& operator=(FileLock&& other) noexcept;
+
+ // Release the lock if previously acquired.
+ ~FileLock();
+
+ // Acquire lock, blocking. Returns true if acquired, otherwise false.
+ [[nodiscard]] bool acquire();
+
+ // Release lock early. If not previously acquired, nothing happens.
+ void release();
+
+ // Return whether the lock is acquired successfully.
+ bool acquired() const;
+
+private:
+ int m_fd = -1;
+ bool m_acquired = false;
+};
+
+inline FileLock::~FileLock()
+{
+ release();
+}
+
+inline bool
+FileLock::acquired() const
+{
+ return m_acquired;
+}
+
+} // namespace util