From a57e1093ece0959672f8580a374eb43a38a9a716 Mon Sep 17 00:00:00 2001 From: dengbo Date: Mon, 13 Jul 2026 18:49:27 +0800 Subject: [PATCH] feat: add PipeWire socket mount support for sandbox 1. Added `enable-pipewire` flag to CLI for enabling PipeWire socket mount 2. Extended API schema v1 with `enable_pipewire` option in RuntimeConfigure 3. Implemented mount logic in ContainerCfgBuilder to bind host PipeWire socket into container 4. Enhanced container_builder to resolve XDG runtime directory and mount socket 5. Added unit tests covering runtime config and CLI option propagation 6. Updated merge logic for runtime configs to handle PipeWire setting 7. Integrated option flow from CLI, runtime config, and JSON serialization/deserialization Log: Added --enable-pipewire option to mount PipeWire audio socket inside sandbox for audio support Influence: 1. Test CLI flag with `--enable-pipewire` on container run, verify socket is mounted 2. Test runtime config JSON with `enable_pipewire: true`, verify mount behavior 3. Test mount failure when host socket does not exist or is not a socket 4. Test config merge: partial configs, overwrite order, and default behavior 5. Verify socket accessibility inside container (permissions, path) 6. Test combined with other options like --disable-xdp to confirm no conflicts 7. Test on systems with and without PipeWire installed 8. Verify backward compatibility for existing configs without the new field --- api/schema/v1.json | 3 ++ api/schema/v1.yaml | 2 + apps/ll-cli/src/main.cpp | 5 ++ .../src/linglong/api/types/v1/Generators.hpp | 4 ++ .../api/types/v1/RuntimeConfigure.hpp | 1 + libs/linglong/src/linglong/cli/cli.h | 1 + .../linglong/runtime/container_builder.cpp | 19 ++++++++ .../src/linglong/runtime/container_builder.h | 2 + .../runtime/container_builder_test.cpp | 48 +++++++++++++++++++ .../linglong/utils/runtime_config_test.cpp | 9 ++++ .../container_cfg_builder.cpp | 13 +++++ .../container_cfg_builder.h | 7 +++ .../src/linglong/utils/runtime_config.cpp | 4 ++ 13 files changed, 118 insertions(+) diff --git a/api/schema/v1.json b/api/schema/v1.json index 99362b75a..11116f38d 100644 --- a/api/schema/v1.json +++ b/api/schema/v1.json @@ -1458,6 +1458,9 @@ "disable_xdp": { "type": "boolean" }, + "enable_pipewire": { + "type": "boolean" + }, "mounts": { "type": "array", "items": { diff --git a/api/schema/v1.yaml b/api/schema/v1.yaml index 6c87f3f55..ca3e0593c 100644 --- a/api/schema/v1.yaml +++ b/api/schema/v1.yaml @@ -1103,6 +1103,8 @@ $defs: $ref: '#/$defs/DeviceOption' disable_xdp: type: boolean + enable_pipewire: + type: boolean mounts: type: array items: diff --git a/apps/ll-cli/src/main.cpp b/apps/ll-cli/src/main.cpp index 721e1884c..b9c5aab6c 100644 --- a/apps/ll-cli/src/main.cpp +++ b/apps/ll-cli/src/main.cpp @@ -179,6 +179,11 @@ ll-cli run org.deepin.demo -- bash -x /path/to/bash/script)")); runOptions.disableXdp, _("Enable or disable xdg-desktop-portal related integration inside the sandbox")) ->take_last(); + cliRun + ->add_flag("--enable-pipewire", + runOptions.enablePipewireSocketMount, + _("Enable PipeWire socket mount inside the sandbox")) + ->take_last(); cliRun->add_option("--run-context", runOptions.runContext, _("Run context json string")) ->group(""); cliRun diff --git a/libs/api/src/linglong/api/types/v1/Generators.hpp b/libs/api/src/linglong/api/types/v1/Generators.hpp index 0fa78622d..c68e167b3 100644 --- a/libs/api/src/linglong/api/types/v1/Generators.hpp +++ b/libs/api/src/linglong/api/types/v1/Generators.hpp @@ -1313,6 +1313,7 @@ inline void from_json(const json & j, RuntimeConfigure& x) { x.deviceMode = get_stack_optional>(j, "device_mode"); x.devices = get_stack_optional>(j, "devices"); x.disableXdp = get_stack_optional(j, "disable_xdp"); +x.enablePipewireSocketMount = get_stack_optional(j, "enable_pipewire"); x.env = get_stack_optional>(j, "env"); x.extDefs = get_stack_optional>>(j, "ext_defs"); x.instances = get_stack_optional>(j, "instances"); @@ -1330,6 +1331,9 @@ j["devices"] = x.devices; if (x.disableXdp) { j["disable_xdp"] = x.disableXdp; } +if (x.enablePipewireSocketMount) { +j["enable_pipewire"] = x.enablePipewireSocketMount; +} if (x.env) { j["env"] = x.env; } diff --git a/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp b/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp index 5914a9fb2..f3341b64d 100644 --- a/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp +++ b/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp @@ -40,6 +40,7 @@ struct RuntimeConfigure { std::optional> deviceMode; std::optional> devices; std::optional disableXdp; +std::optional enablePipewireSocketMount; std::optional> env; /** * external extension definitions to extend the component diff --git a/libs/linglong/src/linglong/cli/cli.h b/libs/linglong/src/linglong/cli/cli.h index b9f7bcebb..d235a85bd 100644 --- a/libs/linglong/src/linglong/cli/cli.h +++ b/libs/linglong/src/linglong/cli/cli.h @@ -58,6 +58,7 @@ struct RunOptions std::optional workdir; std::vector extensions; std::optional disableXdp; + std::optional enablePipewireSocketMount; bool privileged{ false }; std::vector capsAdd; std::vector cdiSpecDir = { "/etc/cdi", "/var/run/cdi" }; diff --git a/libs/linglong/src/linglong/runtime/container_builder.cpp b/libs/linglong/src/linglong/runtime/container_builder.cpp index 4d2953044..edb58391e 100644 --- a/libs/linglong/src/linglong/runtime/container_builder.cpp +++ b/libs/linglong/src/linglong/runtime/container_builder.cpp @@ -9,6 +9,7 @@ #include "linglong/cli/cli.h" #include "linglong/common/dir.h" #include "linglong/common/strings.h" +#include "linglong/common/xdg.h" #include "linglong/oci-cfg-generators/container_cfg_builder.h" #include "linglong/package/architecture.h" #include "linglong/runtime/run_context.h" @@ -130,6 +131,10 @@ auto RunContainerOptions::applyRuntimeConfig( this->disableXdp = *runtimeConfig.disableXdp; } + if (runtimeConfig.enablePipewireSocketMount.has_value()) { + this->enablePipewireSocketMount = *runtimeConfig.enablePipewireSocketMount; + } + if (runtimeConfig.deviceMode) { for (const auto &option : *runtimeConfig.deviceMode) { if (option == api::types::v1::DeviceOption::Passthru) { @@ -172,6 +177,10 @@ auto RunContainerOptions::applyCliRunOptions(const cli::RunOptions &options) noe if (options.disableXdp.has_value()) { this->disableXdp = *options.disableXdp; } + + if (options.enablePipewireSocketMount.has_value()) { + this->enablePipewireSocketMount = *options.enablePipewireSocketMount; + } this->privileged = options.privileged; this->capabilities.insert(this->capabilities.end(), options.capsAdd.begin(), @@ -211,6 +220,11 @@ auto RunContainerOptions::isDevicePassthruEnabled() const noexcept -> bool return this->devicePassthru; } +auto RunContainerOptions::isPipewireSocketMountEnabled() const noexcept -> bool +{ + return this->enablePipewireSocketMount; +} + auto RunContainerOptions::isXdpDisabled() const noexcept -> bool { return this->disableXdp; @@ -590,6 +604,11 @@ auto ContainerBuilder::configureRunContainer(PreparedContainer &prepared, docMountPoint.error()); } } + if (options.isPipewireSocketMountEnabled()) { + auto pwSocketPath = common::xdg::getXDGRuntimeDir() / "pipewire-0"; + prepared.cfgBuilder.enablePipewireSocketMount( + generator::PipewireMountOption{ .hostSocketPath = std::move(pwSocketPath) }); + } if (options.isDevicePassthruEnabled()) { prepared.cfgBuilder.bindDev(true); diff --git a/libs/linglong/src/linglong/runtime/container_builder.h b/libs/linglong/src/linglong/runtime/container_builder.h index 77c2f454a..a0c32c0f7 100644 --- a/libs/linglong/src/linglong/runtime/container_builder.h +++ b/libs/linglong/src/linglong/runtime/container_builder.h @@ -54,11 +54,13 @@ struct RunContainerOptions -> const std::vector &; [[nodiscard]] auto isDevicePassthruEnabled() const noexcept -> bool; [[nodiscard]] auto isXdpDisabled() const noexcept -> bool; + [[nodiscard]] auto isPipewireSocketMountEnabled() const noexcept -> bool; [[nodiscard]] auto isPrivileged() const noexcept -> bool; CommonContainerOptions common; bool disableXdp{ false }; + bool enablePipewireSocketMount{ false }; bool privileged{ false }; bool devicePassthru{ false }; std::map env; diff --git a/libs/linglong/tests/ll-tests/src/linglong/runtime/container_builder_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/runtime/container_builder_test.cpp index f702e923e..8559b8c67 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/runtime/container_builder_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/runtime/container_builder_test.cpp @@ -80,3 +80,51 @@ TEST(RunContainerOptionsTest, ApplyCliRunOptionsOverridesRuntimeConfigXdpSetting ASSERT_TRUE(cliResult); EXPECT_TRUE(options.isXdpDisabled()); } + +TEST(RunContainerOptionsTest, ApplyRuntimeConfigEnablesPipewireSocketMount) +{ + runtime::RunContainerOptions options; + + api::types::v1::RuntimeConfigure runtimeConfig; + runtimeConfig.enablePipewireSocketMount = true; + + auto result = options.applyRuntimeConfig(runtimeConfig); + ASSERT_TRUE(result); + EXPECT_TRUE(options.isPipewireSocketMountEnabled()); +} + +TEST(RunContainerOptionsTest, ApplyCliRunOptionsPreservesRuntimeConfigPipewireSettingByDefault) +{ + runtime::RunContainerOptions options; + + api::types::v1::RuntimeConfigure runtimeConfig; + runtimeConfig.enablePipewireSocketMount = true; + + auto runtimeResult = options.applyRuntimeConfig(runtimeConfig); + ASSERT_TRUE(runtimeResult); + ASSERT_TRUE(options.isPipewireSocketMountEnabled()); + + cli::RunOptions runOptions; + auto cliResult = options.applyCliRunOptions(runOptions); + ASSERT_TRUE(cliResult); + EXPECT_TRUE(options.isPipewireSocketMountEnabled()); +} + +TEST(RunContainerOptionsTest, ApplyCliRunOptionsOverridesRuntimeConfigPipewireSettingWhenSpecified) +{ + runtime::RunContainerOptions options; + + api::types::v1::RuntimeConfigure runtimeConfig; + runtimeConfig.enablePipewireSocketMount = false; + + auto runtimeResult = options.applyRuntimeConfig(runtimeConfig); + ASSERT_TRUE(runtimeResult); + ASSERT_FALSE(options.isPipewireSocketMountEnabled()); + + cli::RunOptions runOptions; + runOptions.enablePipewireSocketMount = true; + + auto cliResult = options.applyCliRunOptions(runOptions); + ASSERT_TRUE(cliResult); + EXPECT_TRUE(options.isPipewireSocketMountEnabled()); +} 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 223143454..2dcff6b2b 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 @@ -28,6 +28,7 @@ TEST(RuntimeConfigTest, LoadFromPath) RuntimeConfigure config; config.disableXdp = true; + config.enablePipewireSocketMount = true; config.deviceMode = std::vector{ DeviceOption::Passthru }; config.env = std::map{ { "PATH", "/usr/bin" }, { "HOME", "/home/user" } }; @@ -84,6 +85,7 @@ TEST(RuntimeConfigTest, MergeConfigs) RuntimeConfigure config1; config1.disableXdp = false; config1.deviceMode = std::vector{ DeviceOption::Passthru }; + config1.enablePipewireSocketMount = false; config1.devices = std::vector{ "vendor.com/device=gpu0" }; config1.env = std::map{ { "PATH", "/usr/bin" }, { "HOME", "/home/user1" } }; @@ -98,6 +100,7 @@ TEST(RuntimeConfigTest, MergeConfigs) RuntimeConfigure config2; config2.disableXdp = true; config2.deviceMode = std::vector{ DeviceOption::Passthru }; + config2.enablePipewireSocketMount = true; config2.devices = std::vector{ "vendor.com/device=gpu1" }; config2.env = std::map{ { "PATH", "/usr/local/bin" }, { "USER", "testuser" } }; @@ -115,6 +118,8 @@ TEST(RuntimeConfigTest, MergeConfigs) std::vector configs = { config1, config2 }; auto merged = linglong::utils::MergeRuntimeConfig(configs); + ASSERT_TRUE(merged.enablePipewireSocketMount.has_value()); + EXPECT_TRUE(*merged.enablePipewireSocketMount); ASSERT_TRUE(merged.disableXdp.has_value()); EXPECT_TRUE(*merged.disableXdp); @@ -154,6 +159,7 @@ TEST(RuntimeConfigTest, MergeEmptyConfigs) std::vector empty_configs; auto merged = linglong::utils::MergeRuntimeConfig(empty_configs); + EXPECT_FALSE(merged.enablePipewireSocketMount); EXPECT_FALSE(merged.disableXdp); EXPECT_FALSE(merged.deviceMode); EXPECT_FALSE(merged.env); @@ -165,6 +171,7 @@ TEST(RuntimeConfigTest, MergePartialConfigs) // Config with only env RuntimeConfigure config1; config1.disableXdp = false; + config1.enablePipewireSocketMount = false; config1.deviceMode = std::vector{ DeviceOption::Passthru }; config1.env = std::map{ { "PATH", "/usr/bin" } }; @@ -185,6 +192,8 @@ TEST(RuntimeConfigTest, MergePartialConfigs) nlohmann::json j; linglong::api::types::v1::to_json(j, merged); LogD("{}", j.dump()); + ASSERT_TRUE(merged.enablePipewireSocketMount.has_value()); + EXPECT_FALSE(*merged.enablePipewireSocketMount); ASSERT_TRUE(merged.disableXdp.has_value()); EXPECT_FALSE(*merged.disableXdp); diff --git a/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.cpp b/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.cpp index 6bfc426d2..13d4fd62d 100644 --- a/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.cpp +++ b/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.cpp @@ -356,6 +356,12 @@ utils::error::Result ContainerCfgBuilder::buildXDGRuntime() noexcept .source = xdpOption->docMountPoint / "by-app" / appId, .type = "bind" }); } + if (pipewireMountOption) { + bindIfExist(*runMount, + pipewireMountOption->hostSocketPath, + *containerXDGRuntimeDir / "pipewire-0", + false); + } return LINGLONG_OK; } @@ -1563,6 +1569,13 @@ ContainerCfgBuilder &ContainerCfgBuilder::enableXDP(XdpOption option) noexcept return *this; } +ContainerCfgBuilder & +ContainerCfgBuilder::enablePipewireSocketMount(PipewireMountOption option) noexcept +{ + pipewireMountOption = std::move(option); + return *this; +} + utils::error::Result ContainerCfgBuilder::buildContainerInfo() noexcept { LINGLONG_TRACE("build container info"); diff --git a/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.h b/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.h index 7605fe6ee..21a12ab98 100644 --- a/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.h +++ b/libs/oci-cfg-generators/src/linglong/oci-cfg-generators/container_cfg_builder.h @@ -23,6 +23,11 @@ struct XdpOption std::filesystem::path docMountPoint; }; +struct PipewireMountOption +{ + std::filesystem::path hostSocketPath; +}; + enum class ANNOTATION { APPID, BASEDIR, @@ -139,6 +144,7 @@ class ContainerCfgBuilder ContainerCfgBuilder &enableQuirkVolatile() noexcept; ContainerCfgBuilder &enableXDP(XdpOption option) noexcept; + ContainerCfgBuilder &enablePipewireSocketMount(PipewireMountOption option) noexcept; ContainerCfgBuilder & setExtensionMounts(std::vector) noexcept; @@ -330,6 +336,7 @@ class ContainerCfgBuilder std::vector mountpoints; // this 'mounts' is used internally, distinct from config.mounts std::vector mounts; + std::optional pipewireMountOption; std::optional> overlayMerged; diff --git a/libs/utils/src/linglong/utils/runtime_config.cpp b/libs/utils/src/linglong/utils/runtime_config.cpp index 135ce52b0..cff6eecab 100644 --- a/libs/utils/src/linglong/utils/runtime_config.cpp +++ b/libs/utils/src/linglong/utils/runtime_config.cpp @@ -111,6 +111,10 @@ RuntimeConfigure MergeRuntimeConfig(const std::vector &configs result.disableXdp = config.disableXdp; } + if (config.enablePipewireSocketMount.has_value()) { + result.enablePipewireSocketMount = config.enablePipewireSocketMount; + } + if (config.deviceMode) { if (!result.deviceMode) { result.deviceMode = config.deviceMode;