blob: 702cffc91f6e2c13890fde2a8f1e13293689493a [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 "utils/FileMutex.h"
#include <span>
#include <iostream>
#include "minifi-cpp/utils/gsl.h"
#include "utils/OsUtils.h"
#include "utils/Error.h"
#ifdef WIN32
namespace org::apache::nifi::minifi::utils {
FileMutex::FileMutex(std::filesystem::path path): path_(std::move(path)) {}
// we cannot assume the logging system to be initialized
void FileMutex::lock() {
std::lock_guard guard(mtx_);
gsl_Expects(!file_handle_.has_value());
HANDLE handle = CreateFileA(path_.string().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
const auto err = utils::getLastError();
std::string pid_str = "unknown";
handle = CreateFileA(path_.string().c_str(), GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to open file to read pid: " << utils::getLastError().message() << std::endl;
} else {
std::array<char, 16> buffer = {};
size_t pid_str_size = 0;
DWORD read_size;
while (ReadFile(handle, buffer.data() + pid_str_size, gsl::narrow<DWORD>(buffer.size() - pid_str_size), &read_size, NULL) && read_size != 0) {
pid_str_size += read_size;
}
pid_str = "'" + std::string(buffer.data(), pid_str_size) + "'";
if (!CloseHandle(handle)) {
std::cerr << "Failed to close file after unsuccessful locking attempt: " << utils::getLastError().message() << std::endl;
}
}
throw std::system_error{err, "Failed to open file '" + path_.string() + "' to be locked, previous pid: " + pid_str};
}
const std::string pidstr = std::to_string(utils::OsUtils::getCurrentProcessId());
std::span<const char> buffer = pidstr;
while (!buffer.empty()) {
DWORD written;
if (!WriteFile(handle, buffer.data(), gsl::narrow<DWORD>(buffer.size()), &written, NULL)) {
const auto err = utils::getLastError();
if (!CloseHandle(file_handle_.value())) {
std::cerr << "Failed to close file: " << utils::getLastError().message() << std::endl;
}
throw std::system_error(err, "Failed to write pid to lock file '" + path_.string() + "'");
}
buffer = buffer.subspan(written);
}
file_handle_ = handle;
}
void FileMutex::unlock() {
std::lock_guard guard(mtx_);
gsl_Expects(file_handle_.has_value());
if (!CloseHandle(file_handle_.value())) {
std::cerr << "Failed to close file: " << utils::getLastError().message() << std::endl;
}
file_handle_.reset();
}
} // namespace org::apache::nifi::minifi::utils
#else
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
namespace org::apache::nifi::minifi::utils {
FileMutex::FileMutex(std::filesystem::path path): path_(std::move(path)) {}
void FileMutex::lock() {
std::lock_guard guard(mtx_);
gsl_Expects(!file_handle_.has_value());
int flags = O_RDWR | O_CREAT;
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
int fd = open(path_.string().c_str(), flags, 0644);
if (fd < 0) {
throw std::system_error{utils::getLastError(), "Failed to open file '" + path_.string() + "' to be locked"};
}
struct flock file_lock_info{};
file_lock_info.l_type = F_WRLCK;
int value = fcntl(fd, F_SETLK, &file_lock_info);
if (value == -1) {
const auto err = utils::getLastError();
std::string pid_str = "unknown";
std::array<char, 16> buffer{};
size_t pid_str_size = 0;
ssize_t ret = 0;
while ((ret = read(fd, buffer.data() + pid_str_size, buffer.size() - pid_str_size)) > 0) {
pid_str_size += ret;
}
if (ret < 0) {
std::cerr << "Failed to read file content: " << utils::getLastError().message() << std::endl;
} else {
pid_str = "'" + std::string(buffer.data(), pid_str_size) + "'";
}
if (close(fd) == -1) {
std::cerr << "Failed to close file after unsuccessful locking attempt: " << utils::getLastError().message() << std::endl;
}
throw std::system_error{err, "Failed to lock file '" + path_.string() + "', previous pid: " + pid_str};
}
const std::string pidstr = std::to_string(utils::OsUtils::getCurrentProcessId());
std::span<const char> buffer = pidstr;
while (!buffer.empty()) {
ssize_t ret = write(fd, buffer.data(), buffer.size());
if (ret < 0) {
const auto err = utils::getLastError();
if (close(fd) == -1) {
std::cerr << "Failed to close file after unsuccessful pid write attempt: " << utils::getLastError().message() << std::endl;
}
throw std::system_error{err, "Failed to write pid to lock file '" + path_.string() + "'"};
}
buffer = buffer.subspan(ret);
}
file_handle_ = fd;
}
void FileMutex::unlock() {
std::lock_guard guard(mtx_);
gsl_Expects(file_handle_.has_value());
auto file_guard = gsl::finally([&] {
if (close(file_handle_.value()) == -1) {
std::cerr << "Failed to close file after unlock: " << utils::getLastError().message() << std::endl;
}
file_handle_.reset();
});
struct flock file_lock_info{};
file_lock_info.l_type = F_UNLCK;
int value = fcntl(file_handle_.value(), F_SETLK, &file_lock_info);
if (value == -1) {
throw std::system_error{utils::getLastError(), "Failed to unlock file '" + path_.string() + "'"};
}
}
} // namespace org::apache::nifi::minifi::utils
#endif