summaryrefslogtreecommitdiff
path: root/src/utils/cpp/file_locking.cpp
blob: d7f0c49a1c0641642c6c0325c7f8c231f080ef68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 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 <cerrno>  // for errno

#ifdef __unix__
#include <sys/file.h>
#else
#error "Non-unix is not supported yet"
#endif

#include "src/buildtool/file_system/file_system_manager.hpp"
#include "src/buildtool/logging/log_level.hpp"
#include "src/buildtool/logging/logger.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 filename = ToNormalPath(fspath);
        if (not filename.is_absolute()) {
            try {
                filename = std::filesystem::absolute(fspath);
            } catch (std::exception const& e) {
                Logger::Log(LogLevel::Error,
                            "Failed to determine absolute path for lock file "
                            "name {}: {}",
                            fspath.string(),
                            e.what());
                return std::nullopt;
            }
        }
        auto parent = filename.parent_path();
        // create parent folder
        if (not FileSystemManager::CreateDirectory(parent)) {
            return std::nullopt;
        }
        // return lock file name
        return 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;
}