summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/utils/cpp/TARGETS12
-rw-r--r--src/utils/cpp/file_locking.cpp113
-rw-r--r--src/utils/cpp/file_locking.hpp65
3 files changed, 190 insertions, 0 deletions
diff --git a/src/utils/cpp/TARGETS b/src/utils/cpp/TARGETS
index c3c513e2..b835023f 100644
--- a/src/utils/cpp/TARGETS
+++ b/src/utils/cpp/TARGETS
@@ -89,4 +89,16 @@
, "private-deps":
[["src/buildtool/file_system", "file_system_manager"], ["", "libarchive"]]
}
+, "file_locking":
+ { "type": ["@", "rules", "CC", "library"]
+ , "name": ["file_locking"]
+ , "hdrs": ["file_locking.hpp"]
+ , "srcs": ["file_locking.cpp"]
+ , "deps": [["@", "gsl-lite", "", "gsl-lite"]]
+ , "stage": ["src", "utils", "cpp"]
+ , "private-deps":
+ [ ["src/buildtool/file_system", "file_system_manager"]
+ , ["src/utils/cpp", "path"]
+ ]
+ }
}
diff --git a/src/utils/cpp/file_locking.cpp b/src/utils/cpp/file_locking.cpp
new file mode 100644
index 00000000..3868af1b
--- /dev/null
+++ b/src/utils/cpp/file_locking.cpp
@@ -0,0 +1,113 @@
+// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/utils/cpp/file_locking.hpp"
+
+#include <sys/file.h>
+
+#include "src/buildtool/file_system/file_system_manager.hpp"
+#include "src/utils/cpp/path.hpp"
+
+auto LockFile::Acquire(std::filesystem::path const& fspath,
+ bool is_shared) noexcept -> std::optional<LockFile> {
+ static std::mutex lock_mutex{};
+ try {
+ // ensure thread-safety
+ std::unique_lock lock{lock_mutex};
+ // get path to lock file
+ auto lock_file = GetLockFilePath(fspath);
+ if (not lock_file) {
+ return std::nullopt;
+ }
+ // touch lock file
+ if (not FileSystemManager::CreateFile(*lock_file)) {
+ Logger::Log(LogLevel::Error,
+ "LockFile: could not create file {}",
+ lock_file->string());
+ return std::nullopt;
+ }
+ // get open file descriptor
+ gsl::owner<FILE*> file_handle = std::fopen(lock_file->c_str(), "r");
+ if (file_handle == nullptr) {
+ Logger::Log(LogLevel::Error,
+ "LockFile: could not open descriptor for file {}",
+ lock_file->string());
+ return std::nullopt;
+ }
+ // attach flock
+ auto err = flock(fileno(file_handle), is_shared ? LOCK_SH : LOCK_EX);
+ if (err != 0) {
+ Logger::Log(
+ LogLevel::Error,
+ "LockFile: applying lock to file {} failed with errno {}",
+ lock_file->string(),
+ errno);
+ fclose(file_handle);
+ return std::nullopt;
+ }
+ // lock file has been acquired
+ return LockFile(file_handle, *lock_file);
+ } catch (std::exception const& ex) {
+ Logger::Log(
+ LogLevel::Error,
+ "LockFile: acquiring file lock for path {} failed with:\n{}",
+ fspath.string(),
+ ex.what());
+ return std::nullopt;
+ }
+}
+
+LockFile::~LockFile() noexcept {
+ if (file_handle_ != nullptr) {
+ // close open file descriptor
+ fclose(file_handle_);
+ file_handle_ = nullptr;
+ }
+}
+
+auto LockFile::GetLockFilePath(std::filesystem::path const& fspath) noexcept
+ -> std::optional<std::filesystem::path> {
+ try {
+ // bring to normal form
+ auto abs_fspath = ToNormalPath(std::filesystem::absolute(fspath));
+ auto filename = abs_fspath.filename();
+ auto parent = abs_fspath.parent_path();
+ // create parent folder
+ if (not FileSystemManager::CreateDirectory(parent)) {
+ return std::nullopt;
+ }
+ // get lock file name
+ return parent / filename;
+ } catch (std::exception const& ex) {
+ Logger::Log(
+ LogLevel::Error,
+ "LockFile: defining lock file name for path {} failed with:\n{}",
+ fspath.string(),
+ ex.what());
+ return std::nullopt;
+ }
+}
+
+LockFile::LockFile(LockFile&& other) noexcept
+ : file_handle_{other.file_handle_},
+ lock_file_{std::move(other.lock_file_)} {
+ other.file_handle_ = nullptr;
+}
+
+auto LockFile::operator=(LockFile&& other) noexcept -> LockFile& {
+ file_handle_ = other.file_handle_;
+ other.file_handle_ = nullptr;
+ lock_file_ = std::move(other.lock_file_);
+ return *this;
+}
diff --git a/src/utils/cpp/file_locking.hpp b/src/utils/cpp/file_locking.hpp
new file mode 100644
index 00000000..a92b874a
--- /dev/null
+++ b/src/utils/cpp/file_locking.hpp
@@ -0,0 +1,65 @@
+// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef INCLUDED_SRC_OTHER_TOOLS_FILE_LOCKING_HPP
+#define INCLUDED_SRC_OTHER_TOOLS_FILE_LOCKING_HPP
+
+#include <filesystem>
+#include <memory>
+#include <optional>
+
+#include "gsl-lite/gsl-lite.hpp"
+
+/* \brief Thread- and process-safe file locking mechanism for paths.
+ * User guarantees write access in the parent directory of the path given, as
+ * the lock will be placed there and missing tree directories will be created.
+ * Lock path and name conventions: a/b.c, a/b/, a/b => a/b.lock; / => /.lock;
+ */
+class LockFile {
+ public:
+ // no default ctor
+ LockFile() = delete;
+
+ /// \brief Destroy the LockFile object.
+ /// It only closes the open file descriptor, but does not explicitly unlock.
+ ~LockFile() noexcept;
+
+ // no copies, only moves
+ LockFile(LockFile const&) = delete;
+ LockFile(LockFile&& other) noexcept;
+ auto operator=(LockFile const&) = delete;
+ auto operator=(LockFile&& other) noexcept -> LockFile&;
+
+ /// \brief Tries to acquire a lock file in the given directory path.
+ /// Missing directories will be created if write permission exists.
+ /// Returns the lock file object on success, nullopt on failure.
+ [[nodiscard]] static auto Acquire(std::filesystem::path const& fspath,
+ bool is_shared) noexcept
+ -> std::optional<LockFile>;
+
+ private:
+ gsl::owner<FILE*> file_handle_{nullptr};
+ std::filesystem::path lock_file_{};
+
+ /// \brief Private ctor. Instances are only created by Acquire method.
+ explicit LockFile(gsl::owner<FILE*> file_handle,
+ std::filesystem::path lock_file) noexcept
+ : file_handle_{file_handle}, lock_file_{std::move(lock_file)} {};
+
+ [[nodiscard]] static auto GetLockFilePath(
+ std::filesystem::path const& fspath) noexcept
+ -> std::optional<std::filesystem::path>;
+};
+
+#endif // FILE_LOCKING