diff --git a/libs/linglong/src/linglong/package_manager/package_manager.cpp b/libs/linglong/src/linglong/package_manager/package_manager.cpp index e6fd10d3d..cb53779b8 100644 --- a/libs/linglong/src/linglong/package_manager/package_manager.cpp +++ b/libs/linglong/src/linglong/package_manager/package_manager.cpp @@ -1992,7 +1992,19 @@ utils::error::Result PackageManager::removeCache(const package::Reference std::error_code ec; std::filesystem::remove_all(appCache, ec); if (ec) { - return LINGLONG_ERR("failed to remove cache directory", ec); + LogD("failed to remove cache directory {}, retry after fixing permissions: {}", + appCache, + ec.message()); + + auto ret = utils::makeDirectoryTreeRemovable(appCache); + if (!ret) { + return LINGLONG_ERR("failed to make cache directory removable", ret); + } + + std::filesystem::remove_all(appCache, ec); + if (ec) { + return LINGLONG_ERR("failed to remove cache directory", ec); + } } return LINGLONG_OK; diff --git a/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp index 76129bfbb..dfdf6423c 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/utils/file_test.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -13,6 +13,8 @@ #include #include +#include + namespace fs = std::filesystem; class FileTest : public ::testing::Test @@ -277,6 +279,54 @@ TEST_F(FileTest, EnsureDirectory) EXPECT_TRUE(fs::is_directory(multiple_dir)); } +TEST_F(FileTest, MakeDirectoryTreeRemovable) +{ + const fs::perms ownerAll = + fs::perms::owner_read | fs::perms::owner_write | fs::perms::owner_exec; + + fs::path root = dest_dir / "removable"; + fs::path blockedDir = root / "blocked-dir"; + fs::path nestedFile = blockedDir / "nested.txt"; + fs::path blockedFile = root / "blocked-file.txt"; + fs::path writeOnlyFile = root / "write-only.txt"; + + fs::create_directories(blockedDir); + std::ofstream(nestedFile) << "nested"; + std::ofstream(blockedFile) << "blocked"; + std::ofstream(writeOnlyFile) << "write"; + + fs::permissions(root, ownerAll, fs::perm_options::replace); + fs::permissions(blockedDir, fs::perms::none, fs::perm_options::replace); + fs::permissions(blockedFile, fs::perms::none, fs::perm_options::replace); + fs::permissions(writeOnlyFile, fs::perms::owner_write, fs::perm_options::replace); + + if (geteuid() == 0) { + GTEST_SKIP() << "root can remove files regardless of the owner permission bits"; + } + + auto rootPermsBefore = fs::symlink_status(root).permissions(); + + std::error_code removeBeforeEc; + fs::remove_all(root, removeBeforeEc); + ASSERT_TRUE(removeBeforeEc) + << "directory tree should not be removable before permissions are fixed"; + ASSERT_TRUE(fs::exists(root)); + + auto result = linglong::utils::makeDirectoryTreeRemovable(root); + ASSERT_TRUE(result.has_value()) << result.error().message(); + + auto rootPermsAfter = fs::symlink_status(root).permissions(); + EXPECT_EQ(rootPermsAfter & ownerAll, rootPermsBefore & ownerAll); + + auto blockedDirPerms = fs::symlink_status(blockedDir).permissions(); + EXPECT_EQ(blockedDirPerms & ownerAll, ownerAll); + + std::error_code ec; + fs::remove_all(root, ec); + EXPECT_FALSE(ec) << ec.message(); + EXPECT_FALSE(fs::exists(root)); +} + TEST_F(FileTest, RelinkFile) { fs::path target = dest_dir / "target_file"; diff --git a/libs/utils/src/linglong/utils/file.cpp b/libs/utils/src/linglong/utils/file.cpp index 3ea1749be..e4222f52f 100644 --- a/libs/utils/src/linglong/utils/file.cpp +++ b/libs/utils/src/linglong/utils/file.cpp @@ -18,6 +18,30 @@ namespace linglong::utils { +namespace { + +linglong::utils::error::Result +ensureOwnerPermissionsIfNeeded(const std::filesystem::path &path, + std::filesystem::perms currentPerms, + std::filesystem::perms requiredPerms) noexcept +{ + LINGLONG_TRACE("ensure owner permissions if needed"); + + if ((currentPerms & requiredPerms) == requiredPerms) { + return LINGLONG_OK; + } + + std::error_code ec; + std::filesystem::permissions(path, requiredPerms, std::filesystem::perm_options::add, ec); + if (ec) { + return LINGLONG_ERR(fmt::format("failed to change permissions: {}", path), ec); + } + + return LINGLONG_OK; +} + +} // namespace + linglong::utils::error::Result readFile(const std::filesystem::path &filepath) { LINGLONG_TRACE(fmt::format("read file {}", filepath)); @@ -275,6 +299,45 @@ linglong::utils::error::Result ensureDirectory(const std::filesystem::path return LINGLONG_OK; } +linglong::utils::error::Result +makeDirectoryTreeRemovable(const std::filesystem::path &root) noexcept +{ + LINGLONG_TRACE("make directory tree removable"); + + namespace fs = std::filesystem; + + std::error_code ec; + fs::recursive_directory_iterator iter{ root, + fs::directory_options::skip_permission_denied, + ec }; + if (ec) { + return LINGLONG_ERR(fmt::format("failed to iterate directory: {}", root), ec); + } + + const fs::recursive_directory_iterator end; + for (; iter != end; iter.increment(ec)) { + if (ec) { + return LINGLONG_ERR(fmt::format("failed to iterate directory: {}", root), ec); + } + + auto status = iter->symlink_status(ec); + if (ec) { + return LINGLONG_ERR(fmt::format("failed to get entry status: {}", iter->path()), ec); + } + + if (status.type() == fs::file_type::directory) { + auto ret = ensureOwnerPermissionsIfNeeded(iter->path(), + status.permissions(), + fs::perms::owner_all); + if (!ret) { + return ret; + } + } + } + + return LINGLONG_OK; +} + linglong::utils::error::Result relinkFileTo(const std::filesystem::path &link, const std::filesystem::path &target) noexcept { diff --git a/libs/utils/src/linglong/utils/file.h b/libs/utils/src/linglong/utils/file.h index a333e3807..a00b64450 100644 --- a/libs/utils/src/linglong/utils/file.h +++ b/libs/utils/src/linglong/utils/file.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -38,6 +38,9 @@ getFiles(const std::filesystem::path &dir); linglong::utils::error::Result ensureDirectory(const std::filesystem::path &dir); +linglong::utils::error::Result +makeDirectoryTreeRemovable(const std::filesystem::path &root) noexcept; + linglong::utils::error::Result relinkFileTo(const std::filesystem::path &link, const std::filesystem::path &target) noexcept;