mirror of https://github.com/linuxdeepin/linglong
Compare commits
4 Commits
3d7aff57d2
...
3f1e1fce25
| Author | SHA1 | Date |
|---|---|---|
|
|
3f1e1fce25 | |
|
|
41a2650979 | |
|
|
c31b6d74cf | |
|
|
45f13e1813 |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue