Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/linglong/tests/ll-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pfl_add_executable(
src/linglong/package/uab_file_test.cpp
src/linglong/package/version_test.cpp
src/linglong/package/versionv2_test.cpp
src/linglong/oci_cfg_generators/container_cfg_builder_test.cpp
src/linglong/repo/client_factory_test.cpp
src/linglong/repo/config_test.cpp
src/linglong/repo/ostree_repo_test.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <gtest/gtest.h>

#include "../../common/tempdir.h"
#include "linglong/common/strings.h"
#include "linglong/oci-cfg-generators/container_cfg_builder.h"

#include <algorithm>
#include <filesystem>
#include <optional>
#include <string>

using namespace linglong;

namespace {

std::optional<std::string> getEnvValue(const ocppi::runtime::config::types::Config &config,
const std::string &key)
{
if (!config.process.has_value() || !config.process->env.has_value()) {
return std::nullopt;
}

const std::string prefix = key + "=";
for (const auto &entry : *config.process->env) {
if (entry.size() >= prefix.size() && entry.compare(0, prefix.size(), prefix) == 0) {
return entry.substr(prefix.size());
}
}
return std::nullopt;
}

bool containsPart(std::string_view value, std::string_view part)
{
auto parts = common::strings::split(value, ':', common::strings::splitOption::SkipEmpty);
return std::find(parts.begin(), parts.end(), part) != parts.end();
}

std::ptrdiff_t countPart(std::string_view value, std::string_view part)
{
auto parts = common::strings::split(value, ':', common::strings::splitOption::SkipEmpty);
return std::count(parts.begin(), parts.end(), part);
}

} // namespace

// Regression test for issue #1457: the Vulkan ICD loader discovers driver
// manifests by scanning each XDG_DATA_DIRS entry for a "vulkan/icd.d"
// subdirectory. When XDG_DATA_DIRS is not present in the container environment,
// build() must still expose the conventional system data directories so that
// system provided drivers (e.g. the Mesa Turnip/freedreno Vulkan driver under
// /usr/share/vulkan/icd.d) remain discoverable.
TEST(ContainerCfgBuilderEnvTest, XdgDataDirsUnsetGetsSystemDefaults)
{
TempDir tmp;
ASSERT_TRUE(tmp.isValid());
std::filesystem::create_directories(tmp.path() / "bundle");

generator::ContainerCfgBuilder builder;
builder.setAppId("org.deepin.base")
.setBasePath(tmp.path() / "base")
.setBundlePath(tmp.path() / "bundle")
.disablePatch();

auto result = builder.build();
ASSERT_TRUE(result.has_value()) << result.error().message();

auto xdg = getEnvValue(builder.getConfig(), "XDG_DATA_DIRS");
ASSERT_TRUE(xdg.has_value());
EXPECT_TRUE(containsPart(*xdg, "/usr/share"));
EXPECT_TRUE(containsPart(*xdg, "/usr/local/share"));
}

// Regression test for issue #1457: a host XDG_DATA_DIRS that only points at
// Linyaps specific paths may be forwarded into the container, overriding the
// Vulkan loader default search path. The forwarded value must be preserved
// while the standard system data directories are appended so that the loader
// still finds /usr/share/vulkan/icd.d.
TEST(ContainerCfgBuilderEnvTest, XdgDataDirsForwardedHostValueKeepsSystemDefaults)
{
TempDir tmp;
ASSERT_TRUE(tmp.isValid());
std::filesystem::create_directories(tmp.path() / "bundle");

generator::ContainerCfgBuilder builder;
builder.setAppId("org.deepin.base")
.setBasePath(tmp.path() / "base")
.setBundlePath(tmp.path() / "bundle")
.disablePatch()
.appendEnv("XDG_DATA_DIRS", "/var/lib/linglong/entries/share");

auto result = builder.build();
ASSERT_TRUE(result.has_value()) << result.error().message();

auto xdg = getEnvValue(builder.getConfig(), "XDG_DATA_DIRS");
ASSERT_TRUE(xdg.has_value());
EXPECT_TRUE(containsPart(*xdg, "/var/lib/linglong/entries/share"));
EXPECT_TRUE(containsPart(*xdg, "/usr/share"));
EXPECT_TRUE(containsPart(*xdg, "/usr/local/share"));
}

TEST(ContainerCfgBuilderEnvTest, XdgDataDirsDoesNotDuplicateSystemDefaults)
{
TempDir tmp;
ASSERT_TRUE(tmp.isValid());
std::filesystem::create_directories(tmp.path() / "bundle");

generator::ContainerCfgBuilder builder;
builder.setAppId("org.deepin.base")
.setBasePath(tmp.path() / "base")
.setBundlePath(tmp.path() / "bundle")
.disablePatch()
.appendEnv("XDG_DATA_DIRS", "/usr/share:/usr/local/share");

auto result = builder.build();
ASSERT_TRUE(result.has_value()) << result.error().message();

auto xdg = getEnvValue(builder.getConfig(), "XDG_DATA_DIRS");
ASSERT_TRUE(xdg.has_value());
EXPECT_EQ(*xdg, "/usr/share:/usr/local/share");
EXPECT_EQ(countPart(*xdg, "/usr/share"), 1);
EXPECT_EQ(countPart(*xdg, "/usr/local/share"), 1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,41 @@ utils::error::Result<void> ContainerCfgBuilder::buildEnv() noexcept
environment.try_emplace("QT_QPA_PLATFORMTHEME", "xdgdesktopportal");
}

// Ensure the conventional system data directories stay reachable through
// XDG_DATA_DIRS. The host environment (for example a host XDG_DATA_DIRS that
// only points at Linyaps specific paths) may be forwarded into the container,
// overriding the default search path relied on by XDG-data consumers. When
// that happens the Vulkan ICD loader no longer scans /usr/share/vulkan/icd.d
// and fails to discover system provided driver manifests such as the Mesa
// Turnip (freedreno) driver, while OpenGL keeps working because Mesa locates
// DRI drivers through the compiled-in library path. Always keep the standard
// system data directories in XDG_DATA_DIRS so those consumers keep working.
{
const std::vector<std::string> systemDataDirs{ "/usr/local/share", "/usr/share" };
auto xdgIt = environment.find("XDG_DATA_DIRS");
std::string currentValue = (xdgIt != environment.end()) ? xdgIt->second : std::string{};
auto entries =
common::strings::split(currentValue, ':', common::strings::splitOption::SkipEmpty);

std::vector<std::string> dirs;
dirs.reserve(entries.size() + systemDataDirs.size());
for (auto entry : entries) {
dirs.emplace_back(entry);
}

bool changed = (xdgIt == environment.end());
for (const auto &dir : systemDataDirs) {
if (std::find(dirs.begin(), dirs.end(), dir) == dirs.end()) {
dirs.push_back(dir);
changed = true;
}
}

if (changed) {
environment["XDG_DATA_DIRS"] = common::strings::join(dirs, ':');
}
}
Comment on lines +1526 to +1549

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

We can optimize this block to avoid unnecessary string copying and heap allocations:

  1. Use std::string_view for currentValue instead of copying xdgIt->second into a new std::string.
  2. Check if the system directories are already present in entries before allocating and copying elements into dirs. If they are already present, we can skip the entire update process entirely.
        const std::vector<std::string> systemDataDirs{ "/usr/local/share", "/usr/share" };
        auto xdgIt = environment.find("XDG_DATA_DIRS");
        std::string_view currentValue = (xdgIt != environment.end()) ? std::string_view{xdgIt->second} : std::string_view{};
        auto entries =
          common::strings::split(currentValue, ':', common::strings::splitOption::SkipEmpty);

        bool changed = (xdgIt == environment.end());
        if (!changed) {
            for (const auto &dir : systemDataDirs) {
                if (std::find(entries.begin(), entries.end(), dir) == entries.end()) {
                    changed = true;
                    break;
                }
            }
        }

        if (changed) {
            std::vector<std::string> dirs;
            dirs.reserve(entries.size() + systemDataDirs.size());
            for (auto entry : entries) {
                dirs.emplace_back(entry);
            }
            for (const auto &dir : systemDataDirs) {
                if (std::find(dirs.begin(), dirs.end(), dir) == dirs.end()) {
                    dirs.push_back(dir);
                }
            }
            environment["XDG_DATA_DIRS"] = common::strings::join(dirs, ':');
        }


auto envShFile = bundlePath / "00env.sh";
std::ofstream ofs(envShFile);
if (!ofs.is_open()) {
Expand Down
Loading