Compare commits

..

4 Commits

Author SHA1 Message Date
guanzi008 3f1e1fce25
Merge bf24e8397e into 41a2650979 2025-11-17 10:44:17 +08:00
ComixHe 41a2650979 feat: Adding process-aware fileLock implementation
build / ubuntu_24.04 (push) Has been cancelled Details
build / ubuntu_22.04 (push) Has been cancelled Details
coverage / codecov (push) Has been cancelled Details
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
2025-11-13 18:09:32 +08:00
reddevillg c31b6d74cf feat: Disable backtrace by default
The LINYAPS_BACKTRACE environment variable now controls the verbosity of
error messages, including file paths, line numbers, and backtrace details.

Signed-off-by: reddevillg <reddevillg@gmail.com>
2025-11-13 14:14:56 +08:00
dengbo 45f13e1813 fix: the application was abnormally removed due to an error in executing hooks
1. Hook scripts should be executed before exporting and generating caches; otherwise, execution failures may result in the application being removed.
2. Enhances logging for better visibility into hook execution
failures and aligns hook handling across installation, upgrade,
and dependency management workflows.
2025-11-13 14:14:11 +08:00
14 changed files with 1026 additions and 92 deletions

View File

@ -9,6 +9,7 @@ pfl_add_library(
STATIC
SOURCES
# find -regex '\.\/*.+\.[ch]\(pp\)?\(.in\)?' -type f -printf '%P\n'| sort
src/linglong/common/constants.h
src/linglong/common/dir.cpp
src/linglong/common/dir.h
src/linglong/common/display.cpp

View File

@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
constexpr auto default_file_mode = 0644;

View File

@ -688,13 +688,19 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
return;
}
if (!localRef) {
auto newRef = package::Reference::fromPackageInfo(*info);
if (!newRef) {
taskRef.reportError(std::move(newRef).error());
return;
}
auto newRef = package::Reference::fromPackageInfo(*info);
if (!newRef) {
taskRef.reportError(std::move(newRef).error());
return;
}
auto ret = executePostInstallHooks(*newRef);
if (!ret) {
taskRef.reportError(std::move(ret).error());
return;
}
if (!localRef) {
auto generateCacheRet = this->tryGenerateCache(*newRef);
if (!generateCacheRet) {
taskRef.reportError(std::move(generateCacheRet).error());
@ -710,31 +716,19 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
return;
}
auto newRef = package::Reference::fromPackageInfo(*info);
if (!newRef) {
taskRef.reportError(std::move(newRef).error());
return;
}
auto generateCacheRet = this->tryGenerateCache(*newRef);
if (!generateCacheRet) {
taskRef.reportError(std::move(generateCacheRet).error());
return;
}
auto ret = removeAfterInstall(*localRef, *newRef, std::vector{ module });
ret = removeAfterInstall(*localRef, *newRef, std::vector{ module });
if (!ret) {
LogE("failed to remove old reference {} after install {}: {}",
localRef->toString(),
packageRef.toString(),
ret.error().message());
}
ret = executePostInstallHooks(*newRef);
if (!ret) {
taskRef.reportError(std::move(ret).error());
return;
}
};
auto refSpec = fmt::format("{}:{}/{}/{}/{}",
@ -1342,8 +1336,21 @@ void PackageManager::Update(PackageTask &taskContext,
if (tmp.state() != linglong::api::types::v1::State::Succeed) {
LogE("failed to rollback install {}", newRef.toString());
}
auto ret = executePostUninstallHooks(newRef);
if (!ret) {
LogE("failed to rollback execute uninstall hooks: {}", ret.error());
}
});
auto result = executePostInstallHooks(newRef);
if (!result) {
taskContext.updateState(linglong::api::types::v1::State::Failed,
"Failed to execute postInstall hooks.\n"
+ result.error().message());
return;
}
auto oldRefLayerItem = this->repo.getLayerItem(oldRef);
auto msg = fmt::format("Upgrade {} (from repo : {}) to {} (from repo : {}) success",
@ -1377,12 +1384,6 @@ void PackageManager::Update(PackageTask &taskContext,
return;
}
ret = executePostInstallHooks(newRef);
if (!ret) {
qCritical() << "failed to execute post install hooks" << ret.error().message();
return;
}
auto result = this->tryGenerateCache(newRef);
if (!result) {
taskContext.reportError(
@ -1504,14 +1505,14 @@ void PackageManager::pullDependency(PackageTask &taskContext,
transaction.addRollBack([this, remoteRef = *remote, module]() noexcept {
auto result = this->repo.remove(remoteRef.reference, module);
if (!result) {
qCritical() << result.error();
Q_ASSERT(false);
LogE("failed to remove remote reference: {} : {}",
remoteRef.reference.toString(),
result.error().message());
}
result = executePostUninstallHooks(remoteRef.reference);
if (!result) {
qCritical() << result.error();
Q_ASSERT(false);
LogE("failed to rollback execute uninstall hooks: {}", result.error());
}
});
}

View File

@ -114,6 +114,7 @@ public
const std::vector<std::string> &modules) noexcept;
utils::error::Result<void> tryGenerateCache(const package::Reference &ref) noexcept;
utils::error::Result<void> executePostInstallHooks(const package::Reference &ref) noexcept;
utils::error::Result<void> executePostUninstallHooks(const package::Reference &ref) noexcept;
void pullDependency(PackageTask &taskContext,
const api::types::v1::PackageInfoV2 &info,
const std::string &module) noexcept;
@ -169,7 +170,6 @@ private:
Prune(std::vector<api::types::v1::PackageInfoV2> &removedInfo) noexcept;
utils::error::Result<void> generateCache(const package::Reference &ref) noexcept;
utils::error::Result<void> removeCache(const package::Reference &ref) noexcept;
utils::error::Result<void> executePostUninstallHooks(const package::Reference &ref) noexcept;
linglong::repo::OSTreeRepo &repo; // NOLINT
PackageTaskQueue tasks;

View File

@ -206,6 +206,11 @@ utils::error::Result<void> RefInstallationAction::install(PackageTask &task)
if (tmp.state() != linglong::api::types::v1::State::Succeed) {
LogE("failed to rollback install {}", newRef.toString());
}
auto result = pm.executePostUninstallHooks(newRef);
if (!result) {
LogE("failed to rollback execute uninstall hooks: {}", result.error());
}
});
return LINGLONG_OK;
@ -224,6 +229,14 @@ utils::error::Result<void> RefInstallationAction::postInstall(PackageTask &task)
const auto &newRef = operation.newRef->reference;
const auto &oldRef = operation.oldRef;
auto ret = pm.executePostInstallHooks(newRef);
if (!ret) {
task.updateState(linglong::api::types::v1::State::Failed,
"Failed to execute postInstall hooks.\n" + ret.error().message());
return LINGLONG_ERR(ret);
}
auto layer = this->repo.getLayerItem(newRef);
if (!layer) {
task.reportError(
@ -256,13 +269,6 @@ utils::error::Result<void> RefInstallationAction::postInstall(PackageTask &task)
}
}
auto ret = pm.executePostInstallHooks(newRef);
if (!ret) {
task.updateState(linglong::api::types::v1::State::Failed,
"Failed to execute postInstall hooks.\n" + ret.error().message());
return LINGLONG_ERR(ret);
}
transaction.commit();
task.updateState(linglong::api::types::v1::State::Succeed,
fmt::format("Install {} (from repo: {}) success",

View File

@ -329,6 +329,12 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
const auto &newRef = operation.newRef->reference;
const auto &oldRef = operation.oldRef;
auto ret = pm.executePostInstallHooks(newRef);
if (!ret) {
task.reportError(std::move(ret).error());
return LINGLONG_ERR("failed to execute post install hooks");
}
if (!oldRef) {
auto mergeRet = repo.mergeModules();
if (!mergeRet) {
@ -360,12 +366,6 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
}
}
auto ret = pm.executePostInstallHooks(newRef);
if (!ret) {
task.reportError(std::move(ret).error());
return LINGLONG_ERR("failed to execute post install hooks");
}
transaction.commit();
task.updateState(linglong::api::types::v1::State::Succeed, "install uab successfully");
@ -426,6 +426,11 @@ utils::error::Result<void> UabInstallationAction::installUabLayer(
if (!ret) {
LogE("rollback importLayerDir failed: {}", ret.error());
}
ret = pm.executePostUninstallHooks(ref);
if (!ret) {
LogE("failed to rollback execute uninstall hooks: {}", ret.error());
}
});
}
@ -508,4 +513,4 @@ utils::error::Result<void> UabInstallationAction::installDistributionModeUAB()
return LINGLONG_OK;
}
} // namespace linglong::service
} // namespace linglong::service

View File

@ -12,6 +12,7 @@ pfl_add_executable(
DISABLE_INSTALL
SOURCES
# find -regex '\./src/.+\.[ch]\(pp\)?' -type f -printf '%P\n'| sort
src/common/tempdir.h
src/linglong/builder/linglong_builder_test.cpp
src/linglong/builder/source_fetcher_test.cpp
src/linglong/common/strings_test.cpp
@ -24,6 +25,8 @@ pfl_add_executable(
src/linglong/package/architecture_test.cpp
src/linglong/package/fallback_version_test.cpp
src/linglong/package/layer_packager_test.cpp
src/linglong/package_manager/action_test.cpp
src/linglong/package_manager/uab_installation_test.cpp
src/linglong/package/reference_test.cpp
src/linglong/package/semver_compare_test.cpp
src/linglong/package/semver_increment_test.cpp
@ -33,8 +36,6 @@ pfl_add_executable(
src/linglong/package/uab_file_test.cpp
src/linglong/package/version_test.cpp
src/linglong/package/versionv2_test.cpp
src/linglong/package_manager/action_test.cpp
src/linglong/package_manager/uab_installation_test.cpp
src/linglong/repo/client_factory_test.cpp
src/linglong/repo/config_test.cpp
src/linglong/repo/ostree_repo_test.cpp
@ -42,6 +43,7 @@ pfl_add_executable(
src/linglong/utils/command_test.cpp
src/linglong/utils/error/error_test.cpp
src/linglong/utils/file.cpp
src/linglong/utils/filelock_test.cpp
src/linglong/utils/gkeyfile_wrapper_test.cpp
src/linglong/utils/log.cpp
src/linglong/utils/namespce.cpp

View File

@ -7,6 +7,7 @@
#include <gtest/gtest.h>
#include "linglong/common/strings.h"
#include "linglong/utils/command/env.h"
#include "linglong/utils/error/error.h"
#include <filesystem>
@ -14,9 +15,11 @@
using namespace linglong::utils::error;
using namespace linglong::common;
using linglong::utils::command::EnvironmentVariableGuard;
TEST(Error, New)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR");
return LINGLONG_ERR("message", -1);
@ -31,6 +34,7 @@ TEST(Error, New)
TEST(Error, WrapResult)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR");
auto res = []() -> Result<void> {
@ -59,6 +63,7 @@ TEST(Error, WrapResult)
TEST(Error, WrapError)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR");
auto res = []() -> Result<void> {
@ -82,6 +87,7 @@ TEST(Error, WrapError)
TEST(Error, WarpErrorCode)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR");
return LINGLONG_ERR("app install failed", ErrorCode::AppInstallFailed);
@ -95,6 +101,7 @@ TEST(Error, WarpErrorCode)
TEST(Error, WarpStdErrorCode)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR");
std::error_code ec;
@ -112,6 +119,7 @@ TEST(Error, WarpStdErrorCode)
TEST(Error, WarpQtFile)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR");
QFile file("/not_exists_file");
@ -127,10 +135,9 @@ TEST(Error, WarpQtFile)
ASSERT_TRUE(strings::contains(msg, "No such file or directory")) << msg;
}
// 在现有的 error_test.cpp 文件末尾添加以下测试
TEST(Error, WarpGError)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with GError");
g_autoptr(GError) gErr =
@ -147,6 +154,7 @@ TEST(Error, WarpGError)
TEST(Error, WarpSystemError)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with std::system_error");
try {
@ -166,6 +174,7 @@ TEST(Error, WarpSystemError)
TEST(Error, WarpExceptionPtr)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with std::exception_ptr");
std::exception_ptr eptr;
@ -202,6 +211,7 @@ TEST(Error, WarpExceptionPtr)
TEST(Error, WarpStdException)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with std::exception");
std::invalid_argument e("Invalid argument provided");
@ -217,6 +227,7 @@ TEST(Error, WarpStdException)
TEST(Error, WarpNestedError)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
auto innerFn = []() -> Result<void> {
LINGLONG_TRACE("inner function");
@ -242,6 +253,7 @@ TEST(Error, WarpNestedError)
TEST(Error, WarpErrorWithCustomCode)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with custom error code");
return LINGLONG_ERR("Custom error occurred", 12345);
@ -256,6 +268,7 @@ TEST(Error, WarpErrorWithCustomCode)
TEST(Error, WarpQStringMessage)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with QString message");
QString errorMsg = QString("Error occurred at line %1").arg(__LINE__);
@ -271,6 +284,7 @@ TEST(Error, WarpQStringMessage)
TEST(Error, WarpStdStringMessage)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with std::string message");
std::string errorMsg = "Standard string error message";
@ -286,6 +300,7 @@ TEST(Error, WarpStdStringMessage)
TEST(Error, WarpEmptyFile)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test LINGLONG_ERR with empty QFile");
QFile emptyFile;
@ -301,6 +316,7 @@ TEST(Error, WarpEmptyFile)
TEST(Error, SuccessCase)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
auto res = []() -> Result<void> {
LINGLONG_TRACE("test successful operation");
// 模拟成功操作
@ -312,6 +328,7 @@ TEST(Error, SuccessCase)
TEST(Error, ErrorCodeEnumValues)
{
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
// 测试各种错误码枚举值
auto testErrorCode = [](ErrorCode code, const std::string &description) -> Result<void> {
LINGLONG_TRACE("test error code : " + description);
@ -331,3 +348,45 @@ TEST(Error, ErrorCodeEnumValues)
ASSERT_FALSE(res3.has_value());
ASSERT_EQ(res3.error().code(), 0); // Success 错误码为0
}
TEST(Error, NoBacktrace)
{
auto res = []() -> Result<void> {
LINGLONG_TRACE("trace");
return LINGLONG_ERR("error");
}();
ASSERT_FALSE(res.has_value());
auto msg = res.error().message();
ASSERT_FALSE(strings::contains(msg, "trace")) << msg;
ASSERT_TRUE(strings::contains(msg, "error")) << msg;
}
TEST(Error, DefaultError)
{
auto res1 = []() -> Result<void> {
LINGLONG_TRACE("trace 1");
return LINGLONG_ERR("error 1", 1);
};
auto res2 = [&res1]() -> Result<void> {
LINGLONG_TRACE("trace 2");
return LINGLONG_ERR(res1());
}();
ASSERT_FALSE(res2.has_value());
auto msg = res2.error().message();
ASSERT_FALSE(strings::contains(msg, "trace")) << msg;
ASSERT_TRUE(strings::contains(msg, "error 1")) << msg;
ASSERT_EQ(res2.error().code(), 1);
auto res3 = [&res1]() -> Result<void> {
LINGLONG_TRACE("trace 3");
return LINGLONG_ERR("error 3", res1());
}();
ASSERT_FALSE(res3.has_value());
msg = res3.error().message();
ASSERT_FALSE(strings::contains(msg, "trace")) << msg;
ASSERT_TRUE(strings::contains(msg, "error 3")) << msg;
ASSERT_EQ(res3.error().code(), 1);
}

View File

@ -0,0 +1,428 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "linglong/utils/error/error.h"
#include "linglong/utils/filelock.h"
#include <chrono>
#include <filesystem>
#include <fstream>
#include <sys/wait.h>
#include <unistd.h>
using namespace linglong::utils::filelock;
using namespace linglong::utils::error;
namespace {
// Helper function to create a temporary file path
std::filesystem::path GetTempFilePath()
{
return std::filesystem::temp_directory_path() / ("test_filelock.lock");
}
} // namespace
// Test fixture for FileLock tests
class FileLockTest : public ::testing::Test
{
protected:
std::filesystem::path temp_path;
void SetUp() override
{
temp_path = GetTempFilePath();
// Ensure the file does not exist initially
std::filesystem::remove(temp_path);
}
void TearDown() override { std::filesystem::remove(temp_path); }
};
// Test creating FileLock with create_if_missing = true
TEST_F(FileLockTest, CreateWithMissingFile)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
EXPECT_TRUE(std::filesystem::exists(temp_path));
const auto &lock = std::move(result).value();
EXPECT_EQ(lock.type(), LockType::Read); // Default type
EXPECT_FALSE(lock.isLocked());
}
// Test creating FileLock without create_if_missing when file missing
TEST_F(FileLockTest, CreateWithoutMissingFileFails)
{
auto result = FileLock::create(temp_path, false);
ASSERT_FALSE(result);
EXPECT_THAT(result.error().message(),
::testing::HasSubstr("open file failed: No such file or directory"));
}
// Test creating FileLock on existing file
TEST_F(FileLockTest, CreateOnExistingFile)
{
std::ofstream file(temp_path);
file.close();
auto result = FileLock::create(temp_path, false);
ASSERT_TRUE(result);
const auto &lock = std::move(result).value();
EXPECT_FALSE(lock.isLocked());
}
// Test lock and unlock for Read lock
TEST_F(FileLockTest, LockUnlockRead)
{
auto result = FileLock::create(temp_path);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
auto lock_result = lock.lock(LockType::Read);
ASSERT_TRUE(lock_result) << lock_result.error().message();
EXPECT_TRUE(lock.isLocked());
EXPECT_EQ(lock.type(), LockType::Read);
auto unlock_result = lock.unlock();
ASSERT_TRUE(unlock_result);
EXPECT_FALSE(lock.isLocked());
}
// Test lock and unlock for Write lock
TEST_F(FileLockTest, LockUnlockWrite)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
auto lock_result = lock.lock(LockType::Write);
ASSERT_TRUE(lock_result);
EXPECT_TRUE(lock.isLocked());
EXPECT_EQ(lock.type(), LockType::Write);
auto unlock_result = lock.unlock();
ASSERT_TRUE(unlock_result);
EXPECT_FALSE(lock.isLocked());
}
// Test tryLock success
TEST_F(FileLockTest, TryLockSuccess)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
auto try_result = lock.tryLock(LockType::Write);
ASSERT_TRUE(try_result);
EXPECT_TRUE(*try_result);
EXPECT_TRUE(lock.isLocked());
EXPECT_TRUE(lock.unlock());
}
// Test tryLock failure when already locked by another process
TEST_F(FileLockTest, TryLockFailureInterProcess)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock1 = std::move(result).value();
EXPECT_TRUE(lock1.lock(LockType::Write));
EXPECT_TRUE(lock1.isLocked());
const pid_t pid = fork();
if (pid == 0) { // Child process
auto result_child = FileLock::create(temp_path, false);
if (!result_child) {
_exit(1);
}
auto lock_child = std::move(result_child).value();
auto try_result = lock_child.tryLock(LockType::Write);
if (!try_result || *try_result) {
_exit(1);
}
_exit(0);
} else if (pid > 0) { // Parent
int status{ 0 };
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(WEXITSTATUS(status), 0);
} else {
FAIL() << "Fork failed";
}
}
// Test tryLockFor with timeout success
TEST_F(FileLockTest, TryLockForSuccess)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
auto try_for_result = lock.tryLockFor(LockType::Write, std::chrono::milliseconds(100));
ASSERT_TRUE(try_for_result);
EXPECT_TRUE(*try_for_result);
EXPECT_TRUE(lock.isLocked());
}
// Test tryLockFor with timeout failure
TEST_F(FileLockTest, TryLockForFailure)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock1 = std::move(result).value();
EXPECT_TRUE(lock1.lock(LockType::Write));
EXPECT_TRUE(lock1.isLocked());
const pid_t pid = fork();
if (pid == 0) { // Child
auto result_child = FileLock::create(temp_path, false);
if (!result_child) {
_exit(1);
}
auto lock_child = std::move(result_child).value();
auto try_for_result =
lock_child.tryLockFor(LockType::Write, std::chrono::milliseconds(100));
if (!try_for_result || *try_for_result) {
_exit(1);
}
_exit(0);
} else if (pid > 0) {
int status{ 0 };
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(WEXITSTATUS(status), 0);
} else {
FAIL() << "Fork failed";
}
}
// Test relock from Read to Write
TEST_F(FileLockTest, RelockReadToWrite)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
EXPECT_TRUE(lock.lock(LockType::Read));
EXPECT_EQ(lock.type(), LockType::Read);
auto relock_result = lock.relock(LockType::Write);
ASSERT_TRUE(relock_result);
EXPECT_EQ(lock.type(), LockType::Write);
EXPECT_TRUE(lock.isLocked());
}
// Test relock from Write to Read
TEST_F(FileLockTest, RelockWriteToRead)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
EXPECT_TRUE(lock.lock(LockType::Write));
EXPECT_EQ(lock.type(), LockType::Write);
auto relock_result = lock.relock(LockType::Read);
ASSERT_TRUE(relock_result);
EXPECT_EQ(lock.type(), LockType::Read);
EXPECT_TRUE(lock.isLocked());
}
// Test relock when not locked fails
TEST_F(FileLockTest, RelockNotLocked)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
auto relock_result = lock.relock(LockType::Write);
// Since not locked, relock should just return OK if same type, but test behavior
// Actually, relock assumes locked, but code checks if type == new_type, returns OK
// But to test properly:
EXPECT_TRUE(
relock_result); // Since type_ is Read by default, changing to Write would attempt to lock
// Wait, relock is for changing type while locked.
// If not locked, it shouldn't be called, but let's see code: relock doesn't check if locked.
// It just sets the new flock.
// So if not locked, it would effectively lock it.
auto lock_result = lock.lock(LockType::Read);
ASSERT_TRUE(lock_result);
auto relock = lock.relock(LockType::Read);
ASSERT_TRUE(relock);
}
// Test multiple read locks possible (shared)
TEST_F(FileLockTest, MultipleReadLocks)
{
auto result1 = FileLock::create(temp_path);
ASSERT_TRUE(result1);
auto lock1 = std::move(result1).value();
EXPECT_TRUE(lock1.lock(LockType::Read));
const auto pid = ::fork();
if (pid == 0) {
auto result2 = FileLock::create(temp_path);
if (!result2) {
_exit(1);
}
auto lock2 = std::move(result2).value();
if (auto ret = lock2.lock(LockType::Read); !ret) {
_exit(2);
}
_exit(0);
} else if (pid > 0) {
int status{ 0 };
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(WEXITSTATUS(status), 0);
} else {
FAIL() << "Fork failed";
}
}
// Test write lock exclusive
TEST_F(FileLockTest, WriteLockExclusive)
{
auto result1 = FileLock::create(temp_path);
ASSERT_TRUE(result1);
auto lock1 = std::move(result1).value();
EXPECT_TRUE(lock1.lock(LockType::Write));
const auto pid = ::fork();
if (pid == 0) {
auto result2 = FileLock::create(temp_path, false);
if (!result2) {
_exit(1);
}
auto lock2 = std::move(result2).value();
auto try_read = lock2.tryLock(LockType::Read);
if (!try_read || *try_read) {
_exit(2);
}
auto try_write = lock2.tryLock(LockType::Write);
if (!try_write || *try_write) {
_exit(3);
}
_exit(0);
} else if (pid > 0) {
int status{ 0 };
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(WEXITSTATUS(status), 0);
} else {
FAIL() << "Fork failed";
}
}
// Test behavior after fork
TEST_F(FileLockTest, LockAfterFork)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
EXPECT_TRUE(lock.lock(LockType::Write));
const pid_t pid = fork();
if (pid == 0) { // Child
// In child, lock should not be considered locked because pid changed
if (lock.isLocked()) {
_exit(1);
}
auto lock_result = lock.lock(LockType::Write);
if (lock_result) { // Should fail because "cannot operate on lock from forked process"
_exit(2);
}
_exit(0);
} else if (pid > 0) {
int status{ 0 };
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(WEXITSTATUS(status), 0);
EXPECT_TRUE(lock.isLocked()); // Parent still locked
} else {
FAIL() << "Fork failed";
}
}
// Test cannot create duplicate lock in same process
TEST_F(FileLockTest, NoDuplicateLockSameProcess)
{
auto result1 = FileLock::create(temp_path, true);
ASSERT_TRUE(result1);
auto result2 = FileLock::create(temp_path, false);
ASSERT_FALSE(result2);
EXPECT_THAT(result2.error().message(),
::testing::HasSubstr("process already holds a lock on file"));
}
// Test move constructor
TEST_F(FileLockTest, MoveConstructor)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock1 = std::move(result).value();
EXPECT_TRUE(lock1.lock(LockType::Write));
auto lock2(std::move(lock1));
EXPECT_TRUE(lock2.isLocked());
EXPECT_FALSE(lock1.isLocked()); // Moved from
}
// Test move assignment
TEST_F(FileLockTest, MoveAssignment)
{
auto result1 = FileLock::create(temp_path, true);
ASSERT_TRUE(result1);
auto lock1 = std::move(result1).value();
EXPECT_TRUE(lock1.lock(LockType::Write));
const std::filesystem::path other_path =
std::filesystem::temp_directory_path() / ("other_" + std::to_string(::getpid()) + ".lock");
auto result2 = FileLock::create(other_path, true);
ASSERT_TRUE(result2);
auto lock2 = std::move(result2).value();
lock2 = std::move(lock1);
EXPECT_TRUE(lock2.isLocked());
EXPECT_EQ(lock2.type(), LockType::Write);
EXPECT_FALSE(lock1.isLocked());
}
// Test tryLock when already locked same type
TEST_F(FileLockTest, TryLockAlreadyLockedSameType)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
EXPECT_TRUE(lock.lock(LockType::Read));
auto try_result = lock.tryLock(LockType::Read);
ASSERT_TRUE(try_result);
}
// Test lock when already locked different type
TEST_F(FileLockTest, LockAlreadyLockedDiffType)
{
auto result = FileLock::create(temp_path, true);
ASSERT_TRUE(result);
auto lock = std::move(result).value();
EXPECT_TRUE(lock.lock(LockType::Read));
auto lock_result = lock.lock(LockType::Write);
ASSERT_FALSE(lock_result);
EXPECT_THAT(lock_result.error().message(),
::testing::HasSubstr("use relock to change lock type"));
}

View File

@ -9,10 +9,11 @@ pfl_add_library(
STATIC
SOURCES
# find -regex '\.\/*.+\.[ch]\(pp\)?\(.in\)?' -type f -printf '%P\n'| sort
src/linglong/utils/command/env.cpp
src/linglong/utils/command/env.h
src/linglong/utils/bash_command_helper.h
src/linglong/utils/command/cmd.cpp
src/linglong/utils/command/cmd.h
src/linglong/utils/command/env.cpp
src/linglong/utils/command/env.h
src/linglong/utils/command/ocppi-helper.cpp
src/linglong/utils/command/ocppi-helper.h
src/linglong/utils/dbus/log.cpp
@ -25,11 +26,18 @@ pfl_add_library(
src/linglong/utils/error/details/error_impl.h
src/linglong/utils/error/error.cpp
src/linglong/utils/error/error.h
src/linglong/utils/file.cpp
src/linglong/utils/file.h
src/linglong/utils/filelock.cpp
src/linglong/utils/filelock.h
src/linglong/utils/finally/finally.cpp
src/linglong/utils/finally/finally.h
src/linglong/utils/gettext.h
src/linglong/utils/gkeyfile_wrapper.h
src/linglong/utils/global/initialize.cpp
src/linglong/utils/global/initialize.h
src/linglong/utils/hooks.cpp
src/linglong/utils/hooks.h
src/linglong/utils/log/formatter.cpp
src/linglong/utils/log/formatter.h
src/linglong/utils/log/log.cpp
@ -46,14 +54,8 @@ pfl_add_library(
src/linglong/utils/serialize/yaml.h
src/linglong/utils/transaction.cpp
src/linglong/utils/transaction.h
src/linglong/utils/file.cpp
src/linglong/utils/file.h
src/linglong/utils/xdg/directory.h
src/linglong/utils/xdg/directory.cpp
src/linglong/utils/gkeyfile_wrapper.h
src/linglong/utils/hooks.cpp
src/linglong/utils/hooks.h
src/linglong/utils/bash_command_helper.h
src/linglong/utils/xdg/directory.h
COMPILE_FEATURES
PUBLIC
cxx_std_17

View File

@ -6,7 +6,10 @@
#pragma once
#include <fmt/format.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@ -18,14 +21,17 @@ public:
ErrorImpl(const char *file,
int line,
int code,
std::string msg,
std::string trace_msg,
std::optional<std::string> msg,
std::unique_ptr<ErrorImpl> cause = nullptr)
: _code(code)
, _msg(std::move(msg))
, _file(file)
, _line(line)
, cause(std::move(cause))
, _trace_msg(std::move(trace_msg))
{
backtrace = (getenv("LINYAPS_BACKTRACE") != nullptr);
}
[[nodiscard]] auto code() const -> int { return _code; };
@ -37,17 +43,31 @@ public:
if (!msg.empty()) {
msg += "\n";
}
msg += err->_file + ":" + std::to_string(err->_line) + " " + err->_msg;
if (backtrace) {
auto trace_msg = err->_trace_msg;
if (err->_msg) {
trace_msg += ": " + *err->_msg;
}
msg += fmt::format("{}:{} {}", err->_file, err->_line, trace_msg);
} else {
if (err->_msg) {
msg = *err->_msg;
break;
}
}
}
return msg;
}
private:
int _code;
std::string _msg;
std::optional<std::string> _msg;
std::string _file;
int _line;
std::unique_ptr<ErrorImpl> cause;
std::string _trace_msg;
bool backtrace = false;
};
} // namespace linglong::utils::error::details

View File

@ -83,7 +83,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
static_cast<int>(code),
trace_msg + ": " + msg.toStdString(),
trace_msg,
msg.toStdString(),
nullptr));
}
@ -96,7 +97,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
static_cast<int>(code),
trace_msg + ": " + msg,
trace_msg,
msg,
nullptr));
}
@ -109,7 +111,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
static_cast<int>(code),
trace_msg + ": " + msg,
trace_msg,
msg,
nullptr));
}
@ -120,7 +123,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
code,
trace_msg + ": " + msg.toStdString(),
trace_msg,
msg.toStdString(),
nullptr));
}
@ -131,7 +135,7 @@ public:
int code = -1) -> Error
{
return Error(
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg + ": " + msg, nullptr));
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, msg, nullptr));
}
static auto
@ -139,7 +143,7 @@ public:
-> Error
{
return Error(
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg + ": " + msg, nullptr));
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, msg, nullptr));
}
static auto Err(const char *file,
@ -148,12 +152,13 @@ public:
const QString &msg,
const QFile &qfile) -> Error
{
return Error(std::make_unique<details::ErrorImpl>(
file,
line,
qfile.error(),
trace_msg + ": " + msg.toStdString() + ": " + qfile.errorString().toStdString(),
nullptr));
return Error(std::make_unique<details::ErrorImpl>(file,
line,
qfile.error(),
trace_msg,
msg.toStdString() + ": "
+ qfile.errorString().toStdString(),
nullptr));
}
static auto Err(const char *file, int line, const std::string &trace_msg, const QFile &qfile)
@ -162,8 +167,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
qfile.error(),
trace_msg + ": "
+ qfile.fileName().toStdString() + ": "
trace_msg,
qfile.fileName().toStdString() + ": "
+ qfile.errorString().toStdString(),
nullptr));
}
@ -174,16 +179,17 @@ public:
std::exception_ptr err,
int code = -1) -> Error
{
std::string what = trace_msg + ": ";
std::string what;
try {
std::rethrow_exception(std::move(err));
} catch (const std::exception &e) {
what += e.what();
what = e.what();
} catch (...) {
what += "unknown";
what = "unknown";
}
return Error(std::make_unique<details::ErrorImpl>(file, line, code, what, nullptr));
return Error(
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, what, nullptr));
}
static auto Err(const char *file,
@ -193,7 +199,7 @@ public:
std::exception_ptr err,
int code = -1) -> Error
{
std::string what = trace_msg + ": " + msg.toStdString() + ": ";
std::string what = msg.toStdString() + ": ";
try {
std::rethrow_exception(std::move(err));
} catch (const std::exception &e) {
@ -202,18 +208,15 @@ public:
what += "unknown";
}
return Error(std::make_unique<details::ErrorImpl>(file, line, code, what, nullptr));
return Error(
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, what, nullptr));
}
static auto
Err(const char *file, int line, const std::string &trace_msg, const std::exception &e) -> Error
{
return Error(std::make_unique<details::ErrorImpl>(file,
line,
-1,
trace_msg + ": " + e.what(),
nullptr));
return Error(
std::make_unique<details::ErrorImpl>(file, line, -1, trace_msg, e.what(), nullptr));
}
static auto Err(const char *file,
@ -223,9 +226,10 @@ public:
const std::exception &e,
int code = -1) -> Error
{
std::string what = trace_msg + ": " + msg.toStdString() + ": " + e.what();
std::string what = msg.toStdString() + ": " + e.what();
return Error(std::make_unique<details::ErrorImpl>(file, line, code, what, nullptr));
return Error(
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, what, nullptr));
}
static auto Err(const char *file,
@ -281,7 +285,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
cause.error().code(),
trace_msg + ": " + msg.toStdString(),
trace_msg,
msg.toStdString(),
std::move(cause.error().pImpl)));
}
@ -297,7 +302,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
cause.error().code(),
trace_msg + ": " + msg,
trace_msg,
msg,
std::move(cause.error().pImpl)));
}
@ -313,7 +319,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
cause.error().code(),
trace_msg + ": " + msg,
trace_msg,
msg,
std::move(cause.error().pImpl)));
}
@ -329,6 +336,7 @@ public:
line,
cause.error().code(),
trace_msg,
std::nullopt,
std::move(cause.error().pImpl)));
}
@ -341,7 +349,8 @@ public:
return Error(std::make_unique<details::ErrorImpl>(file,
line,
cause.code(),
trace_msg + ": " + msg,
trace_msg,
msg,
std::move(cause.pImpl)));
}
@ -353,6 +362,7 @@ public:
line,
cause.code(),
trace_msg,
std::nullopt,
std::move(cause.pImpl)));
}

View File

@ -0,0 +1,287 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "linglong/utils/filelock.h"
#include "linglong/common/constants.h"
#include "linglong/utils/finally/finally.h"
#include "linglong/utils/log/log.h"
#include <fmt/format.h>
#include <filesystem>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
namespace linglong::utils::filelock {
pid_t FileLock::pid() const noexcept
{
return pid_;
}
utils::error::Result<FileLock> FileLock::create(std::filesystem::path path,
bool create_if_missing) noexcept
{
LINGLONG_TRACE("create file lock");
auto cur_pid = ::getpid();
auto &[global_pid, locked_paths] = process_locked_paths;
if (global_pid != cur_pid) {
global_pid = cur_pid;
locked_paths.clear();
}
std::error_code ec;
auto abs_path = std::filesystem::absolute(path, ec);
if (ec) {
return LINGLONG_ERR(fmt::format("canonicalize path {} error: {}", path, ec.message()));
}
if (locked_paths.find(abs_path) != locked_paths.end()) {
return LINGLONG_ERR(fmt::format("process already holds a lock on file {}", abs_path));
}
unsigned int flags = O_RDWR | O_CLOEXEC | O_NOFOLLOW;
if (create_if_missing) {
flags |= O_CREAT;
}
auto fd = ::open(abs_path.c_str(), flags, default_file_mode);
if (fd < 0) {
return LINGLONG_ERR(fmt::format("open file failed: {}", ::strerror(errno)));
}
locked_paths[abs_path] = true;
FileLock lock(fd, std::move(abs_path));
return lock;
}
FileLock::~FileLock() noexcept
{
if (isLocked()) {
auto ret = unlock();
if (!ret) {
LogW("unlock file failed: {}", ret.error());
}
}
if (fd > 0 && ::close(fd) < 0) {
LogW("close file failed: {}", ::strerror(errno));
}
fd = -1;
auto it = process_locked_paths.second.find(path);
if (it != process_locked_paths.second.end()) {
process_locked_paths.second.erase(it);
}
}
FileLock::FileLock(int fd, std::filesystem::path path) noexcept
: fd(fd)
, path(std::move(path))
{
}
FileLock::FileLock(FileLock &&other) noexcept
: type_(other.type_)
, fd(other.fd)
, path(std::move(other.path))
{
if (other.pid_ != pid()) {
LogF("move lock to different process");
}
locked.store(other.locked.load(std::memory_order_relaxed), std::memory_order_relaxed);
other.locked.store(false, std::memory_order_relaxed);
other.fd = -1;
}
FileLock &FileLock::operator=(FileLock &&other) noexcept
{
if (this == &other) {
return *this;
}
if (other.pid_ != pid()) {
LogF("move lock to different process");
}
fd = other.fd;
path = std::move(other.path);
type_ = other.type_;
locked.store(other.locked.load(std::memory_order_relaxed), std::memory_order_relaxed);
other.locked.store(false, std::memory_order_relaxed);
other.fd = -1;
return *this;
}
utils::error::Result<void> FileLock::lockCheck() const noexcept
{
LINGLONG_TRACE("validating file lock");
if (pid_ != ::getpid()) {
return LINGLONG_ERR("cannot operate on lock from forked process");
}
return LINGLONG_OK;
}
utils::error::Result<void> FileLock::lock(LockType type) noexcept
{
LINGLONG_TRACE("lock file");
auto ret = lockCheck();
if (!ret) {
return LINGLONG_ERR(ret);
}
if (isLocked()) {
if (type == type_) {
return LINGLONG_OK;
}
return LINGLONG_ERR(
fmt::format("use relock to change lock type from {} to {}", type_, type));
}
struct flock fl{};
fl.l_type = (type == LockType::Write) ? F_WRLCK : F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
while (true) {
if (::fcntl(fd, F_SETLKW, &fl) == 0) {
type_ = type;
locked.store(true, std::memory_order_relaxed);
return LINGLONG_OK;
}
if (errno == EINTR) {
continue;
}
return LINGLONG_ERR(fmt::format("failed to lock file {}: {}", path, ::strerror(errno)));
}
}
utils::error::Result<bool> FileLock::tryLock(LockType type) noexcept
{
LINGLONG_TRACE("try lock file");
auto ret = lockCheck();
if (!ret) {
return LINGLONG_ERR(ret);
}
if (isLocked()) {
if (type == type_) {
return LINGLONG_OK;
}
return LINGLONG_ERR(
fmt::format("use relock to change lock type from {} to {}", type_, type));
}
struct flock fl{};
fl.l_type = (type == LockType::Write) ? F_WRLCK : F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
while (true) {
if (::fcntl(fd, F_SETLK, &fl) == 0) {
type_ = type;
locked.store(true, std::memory_order_relaxed);
return true;
}
if (errno == EINTR) {
continue;
}
if (errno == EACCES || errno == EAGAIN) {
return false;
}
return LINGLONG_ERR(fmt::format("failed to lock file {}: {}", path, ::strerror(errno)));
}
}
utils::error::Result<void> FileLock::unlock() noexcept
{
LINGLONG_TRACE("unlock file");
auto ret = lockCheck();
if (!ret) {
return LINGLONG_ERR(ret);
}
if (!isLocked()) {
return LINGLONG_OK;
}
struct flock fl{};
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
while (true) {
if (::fcntl(fd, F_SETLK, &fl) == 0) {
locked.store(false, std::memory_order_relaxed);
return LINGLONG_OK;
}
if (errno == EINTR) {
continue;
}
return LINGLONG_ERR(fmt::format("failed to unlock file {}: {}", path, ::strerror(errno)));
}
}
utils::error::Result<void> FileLock::relock(LockType new_type) noexcept
{
LINGLONG_TRACE("upgrade lock type");
if (type_ == new_type) {
return LINGLONG_OK;
}
auto ret = lockCheck();
if (!ret) {
return LINGLONG_ERR(ret);
}
struct flock fl{};
fl.l_type = (new_type == LockType::Write) ? F_WRLCK : F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
while (true) {
if (::fcntl(fd, F_SETLKW, &fl) == 0) {
type_ = new_type;
return LINGLONG_OK;
}
if (errno == EINTR) {
continue;
}
return LINGLONG_ERR(fmt::format("failed to relock file {}: {}", path, ::strerror(errno)));
}
}
bool FileLock::isLocked() const noexcept
{
return locked.load(std::memory_order_relaxed) && pid() == ::getpid();
}
} // namespace linglong::utils::filelock

View File

@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include "linglong/utils/error/error.h"
#include <fmt/format.h>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <thread>
#include <unordered_map>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
namespace linglong::utils::filelock {
enum class LockType : uint8_t {
Read,
Write,
};
class FileLock
{
public:
static utils::error::Result<FileLock> create(std::filesystem::path path,
bool create_if_missing = true) noexcept;
~FileLock() noexcept;
FileLock(const FileLock &) = delete;
FileLock &operator=(const FileLock &) = delete;
FileLock(FileLock &&) noexcept;
FileLock &operator=(FileLock &&) noexcept;
utils::error::Result<void> lock(LockType type) noexcept;
utils::error::Result<bool> tryLock(LockType type) noexcept;
template <typename Rep, typename Period>
utils::error::Result<bool>
tryLockFor(LockType type,
const std::chrono::duration<Rep, Period> &timeout = std::chrono::milliseconds(100),
const std::chrono::milliseconds &poll_interval = std::chrono::milliseconds(10))
{
LINGLONG_TRACE("try lock with timeout");
auto deadline = std::chrono::steady_clock::now() + timeout;
while (std::chrono::steady_clock::now() < deadline) {
auto ret = tryLock(type);
if (!ret) {
return LINGLONG_ERR(ret);
}
if (*ret) {
return true;
}
std::this_thread::sleep_for(poll_interval);
}
return false;
}
utils::error::Result<void> relock(LockType new_type) noexcept;
utils::error::Result<void> unlock() noexcept;
[[nodiscard]] LockType type() const noexcept { return type_; }
[[nodiscard]] bool isLocked() const noexcept;
[[nodiscard]] pid_t pid() const noexcept;
private:
[[nodiscard]] utils::error::Result<void> lockCheck() const noexcept;
FileLock(int fd, std::filesystem::path path) noexcept;
pid_t pid_{ ::getpid() };
LockType type_{ LockType::Read };
std::atomic_bool locked{ false };
int fd{ -1 };
std::filesystem::path path;
static inline std::pair<pid_t, std::unordered_map<std::string, bool>> process_locked_paths{
::getpid(), {}
};
};
} // namespace linglong::utils::filelock
template <>
struct fmt::formatter<linglong::utils::filelock::LockType>
{
constexpr auto parse(fmt::format_parse_context &ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const linglong::utils::filelock::LockType &p, FormatContext &ctx) const
{
const auto *msg = p == linglong::utils::filelock::LockType::Read ? "read" : "write";
return fmt::format_to(ctx.out(), "{}", msg);
}
};