From fb0386d3d32758e47b0b895becb04d3100871b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E9=82=A3=E5=87=A0?= <2078634206@qq.com> Date: Wed, 30 Sep 2026 13:18:50 +0800 Subject: [PATCH 1/2] fix(utils): report runtime config probe errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loadRuntimeConfigFromDir ignored the error_code from the config.json exists() probe and the config.d is_directory() probe, silently reporting "no configuration" when the check itself failed (e.g. symlink loop or missing search permission). Check the error codes and return an error, keeping the legitimate no-config case as success with nullopt, and add unit tests. Signed-off-by: 陈那几 <2078634206@qq.com> --- .../linglong/utils/runtime_config_test.cpp | 52 +++++++++++++++++++ .../src/linglong/utils/runtime_config.cpp | 13 ++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/libs/linglong/tests/ll-tests/src/linglong/utils/runtime_config_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/utils/runtime_config_test.cpp index d3cd39bc8..326b32acb 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/utils/runtime_config_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/utils/runtime_config_test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace fs = std::filesystem; using namespace linglong::api::types::v1; @@ -443,3 +444,54 @@ TEST(RuntimeConfigTest, LoadRuntimeConfigWithConfigD) EXPECT_EQ(loadedConfig.devices->at(1), "vendor.com/device=10"); EXPECT_EQ(loadedConfig.devices->at(2), "vendor.com/device=20"); } + +TEST(RuntimeConfigTest, LoadRuntimeConfigWithSymlinkLoop) +{ + TempDir tempDir; + + // A config.json pointing at itself makes the exists() probe fail with + // ELOOP, which must be reported as an error. + fs::path configDir = tempDir.path() / "linglong" / "apps" / "test-app"; + fs::create_directories(configDir); + + fs::path configFile = configDir / "config.json"; + std::error_code ec; + fs::create_symlink(configFile, configFile, ec); + ASSERT_EQ(ec.value(), 0); + + std::vector configDirs = { tempDir.path() / "linglong" }; + auto result = linglong::utils::loadRuntimeConfig(configDirs, "test-app", ""); + EXPECT_FALSE(result); +} + +TEST(RuntimeConfigTest, LoadRuntimeConfigWithoutConfig) +{ + TempDir tempDir; + + fs::path configDir = tempDir.path() / "linglong"; + fs::create_directories(configDir); + + std::vector configDirs = { configDir }; + auto result = linglong::utils::loadRuntimeConfig(configDirs, "test-app", ""); + ASSERT_TRUE(result.has_value()); + EXPECT_FALSE(result->has_value()); +} + +TEST(RuntimeConfigTest, LoadRuntimeConfigWithConfigDSymlinkLoop) +{ + TempDir tempDir; + + // A config.d pointing at itself makes the is_directory() probe fail with + // ELOOP, which must be reported as an error. + fs::path configDir = tempDir.path() / "linglong"; + fs::create_directories(configDir); + + fs::path configDPath = configDir / "config.d"; + std::error_code ec; + fs::create_symlink(configDPath, configDPath, ec); + ASSERT_EQ(ec.value(), 0); + + std::vector configDirs = { configDir }; + auto result = linglong::utils::loadRuntimeConfig(configDirs, "", ""); + EXPECT_FALSE(result); +} diff --git a/libs/utils/src/linglong/utils/runtime_config.cpp b/libs/utils/src/linglong/utils/runtime_config.cpp index 5972a4208..cd6a92425 100644 --- a/libs/utils/src/linglong/utils/runtime_config.cpp +++ b/libs/utils/src/linglong/utils/runtime_config.cpp @@ -10,6 +10,8 @@ #include "linglong/utils/error/error.h" #include "linglong/utils/serialize/json.h" +#include + #include #include #include @@ -38,7 +40,10 @@ loadRuntimeConfigFromDir(const std::filesystem::path &configDir, const std::stri std::vector configs; auto configPath = configBaseDir / "config.json"; std::error_code ec; - if (std::filesystem::exists(configPath, ec)) { + if (auto exists = std::filesystem::exists(configPath, ec); ec) { + return LINGLONG_ERR( + fmt::format("failed to check runtime config {}: {}", configPath.string(), ec.message())); + } else if (exists) { auto config = linglong::utils::loadRuntimeConfig(configPath); if (!config) { return LINGLONG_ERR(config); @@ -47,7 +52,11 @@ loadRuntimeConfigFromDir(const std::filesystem::path &configDir, const std::stri } auto configDPath = configBaseDir / "config.d"; - if (std::filesystem::is_directory(configDPath, ec)) { + if (auto isDir = std::filesystem::is_directory(configDPath, ec); ec) { + return LINGLONG_ERR(fmt::format("failed to check runtime config drop-in dir {}: {}", + configDPath.string(), + ec.message())); + } else if (isDir) { std::vector paths; for (const auto &entry : std::filesystem::directory_iterator( configDPath, From e062d08e348d777e9fd06f079d9fd9c946c5b1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E9=82=A3=E5=87=A0?= <2078634206@qq.com> Date: Wed, 30 Sep 2026 13:50:38 +0800 Subject: [PATCH 2/2] fix(utils): treat not_found as absent when probing runtime config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit exists()/is_directory() report an error_code even for regular ENOENT on POSIX, so checking ec directly turned every absent config.json/config.d into a load error. Probe with status() instead: file_type::none means the status could not be determined (ELOOP, EACCES, ...) and is reported as an error, while not_found keeps meaning absent. Signed-off-by: 陈那几 <2078634206@qq.com> --- libs/utils/src/linglong/utils/runtime_config.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libs/utils/src/linglong/utils/runtime_config.cpp b/libs/utils/src/linglong/utils/runtime_config.cpp index cd6a92425..4de98be2d 100644 --- a/libs/utils/src/linglong/utils/runtime_config.cpp +++ b/libs/utils/src/linglong/utils/runtime_config.cpp @@ -40,10 +40,14 @@ loadRuntimeConfigFromDir(const std::filesystem::path &configDir, const std::stri std::vector configs; auto configPath = configBaseDir / "config.json"; std::error_code ec; - if (auto exists = std::filesystem::exists(configPath, ec); ec) { + // file_type::none means the status itself could not be determined (e.g. ELOOP/EACCES); + // not_found with a set error_code is the regular "absent" case on POSIX. + auto configStatus = std::filesystem::status(configPath, ec); + if (configStatus.type() == std::filesystem::file_type::none) { return LINGLONG_ERR( fmt::format("failed to check runtime config {}: {}", configPath.string(), ec.message())); - } else if (exists) { + } + if (std::filesystem::exists(configStatus)) { auto config = linglong::utils::loadRuntimeConfig(configPath); if (!config) { return LINGLONG_ERR(config); @@ -52,11 +56,13 @@ loadRuntimeConfigFromDir(const std::filesystem::path &configDir, const std::stri } auto configDPath = configBaseDir / "config.d"; - if (auto isDir = std::filesystem::is_directory(configDPath, ec); ec) { + auto configDStatus = std::filesystem::status(configDPath, ec); + if (configDStatus.type() == std::filesystem::file_type::none) { return LINGLONG_ERR(fmt::format("failed to check runtime config drop-in dir {}: {}", configDPath.string(), ec.message())); - } else if (isDir) { + } + if (std::filesystem::is_directory(configDStatus)) { std::vector paths; for (const auto &entry : std::filesystem::directory_iterator( configDPath,