From 880b344472ef30856d0995e943401bfb05dffbe9 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:37:41 +0800 Subject: [PATCH 1/2] fix(builder): propagate stat errors when cleaning build artifacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cleanBuildArtifacts() treated a failing exists() query (error_code set, e.g. EACCES on a parent) as "directory absent" and returned success, silently skipping the cleanup; check the error_code first and return an error, keeping the legitimate not-exist case as success. Signed-off-by: 陈那几 <2078634206@qq.com> --- libs/linglong/src/linglong/builder/linglong_builder.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/linglong/src/linglong/builder/linglong_builder.cpp b/libs/linglong/src/linglong/builder/linglong_builder.cpp index 76a578e53..3677dfa11 100644 --- a/libs/linglong/src/linglong/builder/linglong_builder.cpp +++ b/libs/linglong/src/linglong/builder/linglong_builder.cpp @@ -2231,7 +2231,13 @@ utils::error::Result Builder::cleanBuildArtifacts() noexcept LINGLONG_TRACE("clean build artifacts"); std::error_code ec; - if (!std::filesystem::exists(this->internalDir, ec)) { + auto exists = std::filesystem::exists(this->internalDir, ec); + if (ec) { + return LINGLONG_ERR(fmt::format("failed to check build artifacts directory {}: {}", + this->internalDir.string(), + ec.message())); + } + if (!exists) { return LINGLONG_OK; } From bb21577e63ce032334ee9827dec9e875c17a5fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E9=82=A3=E5=87=A0?= <2078634206@qq.com> Date: Fri, 30 Oct 2026 20:10:28 +0800 Subject: [PATCH 2/2] test(builder): cover the stat-error path of cleanBuildArtifacts add CleanWithStatError next to the other Clean* cases: as a non-root user, strip all bits from the parent of the build directory so the exists() query itself fails with EACCES, skip under root, and assert the call fails with the build directory named in the message; also pass ec into LINGLONG_ERR so the errno is preserved, matching the file.cpp pattern. verified red-green: the new test fails on the commit before the fix (the error was swallowed as success) and passes with it; full LinglongBuilder suite green. --- .../src/linglong/builder/linglong_builder.cpp | 6 ++-- .../builder/linglong_builder_test.cpp | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/libs/linglong/src/linglong/builder/linglong_builder.cpp b/libs/linglong/src/linglong/builder/linglong_builder.cpp index 3677dfa11..a8f3d57ee 100644 --- a/libs/linglong/src/linglong/builder/linglong_builder.cpp +++ b/libs/linglong/src/linglong/builder/linglong_builder.cpp @@ -2233,9 +2233,9 @@ utils::error::Result Builder::cleanBuildArtifacts() noexcept std::error_code ec; auto exists = std::filesystem::exists(this->internalDir, ec); if (ec) { - return LINGLONG_ERR(fmt::format("failed to check build artifacts directory {}: {}", - this->internalDir.string(), - ec.message())); + return LINGLONG_ERR( + fmt::format("failed to check build artifacts directory {}", this->internalDir.string()), + ec); } if (!exists) { return LINGLONG_OK; diff --git a/libs/linglong/tests/ll-tests/src/linglong/builder/linglong_builder_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/builder/linglong_builder_test.cpp index 7217405b9..12259e665 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/builder/linglong_builder_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/builder/linglong_builder_test.cpp @@ -226,3 +226,35 @@ TEST(LinglongBuilder, CleanWithPermissionIssue) ASSERT_TRUE(result.has_value()) << result.error().message(); EXPECT_FALSE(fs::exists(linglongDir)); } + +TEST(LinglongBuilder, CleanWithStatError) +{ + if (geteuid() == 0) { + GTEST_SKIP() << "root bypasses the parent directory search permission bits"; + } + + TempDir workingDir; + + linglong::builder::BuilderMock builder(workingDir.path()); + + namespace fs = std::filesystem; + auto linglongDir = workingDir.path() / "linglong"; + fs::create_directories(linglongDir); + + // Strip every bit from the parent so the exists() query itself fails with EACCES + // instead of reporting "directory absent". + fs::permissions(workingDir.path(), fs::perms::none, fs::perm_options::replace); + + auto result = builder.cleanBuildArtifacts(); + + // Restore access so TempDir can clean up behind us. + std::error_code restoreEc; + fs::permissions(workingDir.path(), + fs::perms::owner_all, + fs::perm_options::replace, + restoreEc); + + ASSERT_FALSE(result.has_value()); + EXPECT_NE(result.error().message().find(linglongDir.string()), std::string::npos) + << result.error().message(); +}