diff --git a/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_real_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_real_test.cpp index 5fa951e18..9647e3ec7 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_real_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_real_test.cpp @@ -11,12 +11,16 @@ #include "linglong/package/layer_dir.h" #include "linglong/package/reference.h" #include "linglong/repo/ostree_repo.h" +#include "linglong/utils/file.h" +#include "linglong/utils/finally/finally.h" #include #include #include #include +#include + namespace linglong::repo::test { namespace fs = std::filesystem; @@ -75,7 +79,8 @@ class RealRepoTest : public ::testing::Test const std::string &version, const std::string &kind, const std::string &module, - const std::vector &relFiles) + const std::vector &relFiles, + const std::vector &readOnlyDirs = {}) { std::error_code ec; auto layerDir = repoRoot / ("layersrc-" + id); @@ -101,6 +106,18 @@ class RealRepoTest : public ::testing::Test nlohmann::json j = info; std::ofstream{ layerDir / "info.json" } << j.dump(); + for (const auto &rel : readOnlyDirs) { + fs::permissions(layerDir / rel, + fs::perms::owner_write | fs::perms::group_write + | fs::perms::others_write, + fs::perm_options::remove, + ec); + if (ec) { + ADD_FAILURE() << "set directory permissions failed: " << ec.message(); + return std::nullopt; + } + } + LayerDir ld(layerDir); if (!ld.valid()) { ADD_FAILURE() << "layer dir should be valid"; @@ -353,6 +370,50 @@ TEST_F(RealRepoTest, CreateTempMergedModuleDirWorks) fs::remove_all(tmp->path(), ec); } +TEST_F(RealRepoTest, TempMergedModuleDirCleansReadOnlyPackageDirectories) +{ + if (::geteuid() == 0) { + GTEST_SKIP() << "root bypasses the directory permissions under test"; + } + + // Restore fixture directories even when the original cleanup leaves files behind. + auto cleanup = utils::finally::finally([&] { + std::ignore = utils::makeDirectoryTreeRemovable(repoRoot); + }); + + auto develop = importLayer("com.example.readonly", + "1.0.0", + "app", + "develop", + { "files/usr/include/readonly.h" }); + ASSERT_TRUE(develop.has_value()); + const std::string readOnlyDir = "files/usr/share/readonly"; + auto binary = importLayer("com.example.readonly", + "1.0.0", + "app", + "binary", + { readOnlyDir + "/data" }, + { readOnlyDir }); + ASSERT_TRUE(binary.has_value()); + auto originalLayer = repo->getLayerDir(*binary, "binary"); + ASSERT_TRUE(originalLayer.has_value()); + const auto originalFile = originalLayer->path() / readOnlyDir / "data"; + const auto originalPermissions = fs::status(originalFile).permissions(); + + fs::path temporaryPath; + { + auto merged = repo->createTempMergedModuleDir(*binary, { "binary", "develop" }); + ASSERT_TRUE(merged.has_value()) << merged.error().message(); + temporaryPath = merged->path(); + ASSERT_TRUE(fs::exists(temporaryPath / readOnlyDir / "data")); + ASSERT_EQ(fs::status(temporaryPath / readOnlyDir).permissions() & fs::perms::owner_write, + fs::perms::none); + } + + EXPECT_FALSE(fs::exists(temporaryPath)); + EXPECT_EQ(fs::status(originalFile).permissions(), originalPermissions); +} + TEST_F(RealRepoTest, ListLocalByQuery) { auto all = repo->listLocalBy(repoCacheQuery{ .id = "com.example.app" }); diff --git a/libs/utils/src/linglong/utils/temporary_directory.cpp b/libs/utils/src/linglong/utils/temporary_directory.cpp index 393980ac8..e973ea3ca 100644 --- a/libs/utils/src/linglong/utils/temporary_directory.cpp +++ b/libs/utils/src/linglong/utils/temporary_directory.cpp @@ -3,6 +3,9 @@ #include "temporary_directory.h" +#include "linglong/utils/file.h" +#include "linglong/utils/log/log.h" + #include #include #include @@ -83,6 +86,20 @@ void TemporaryDirectory::cleanup() noexcept std::error_code ec; std::filesystem::remove_all(directory, ec); + if (ec) { + auto removable = makeDirectoryTreeRemovable(directory); + if (removable) { + ec.clear(); + std::filesystem::remove_all(directory, ec); + } else { + LogW("failed to make temporary directory {} removable: {}", + directory, + removable.error()); + } + if (ec) { + LogW("failed to remove temporary directory {}: {}", directory, ec.message()); + } + } directory.clear(); }