diff --git a/api/schema/v1.json b/api/schema/v1.json index 3408a9500..193edf39e 100644 --- a/api/schema/v1.json +++ b/api/schema/v1.json @@ -1439,6 +1439,12 @@ "type": "string" } }, + "device_mode": { + "type": "array", + "items": { + "$ref": "#/$defs/DeviceOption" + } + }, "ext_defs": { "description": "external extension definitions to extend the component", "type": "object", @@ -1450,6 +1456,12 @@ } } } + }, + "DeviceOption": { + "description": "device option", + "enum": [ + "passthru" + ] } }, "type": "object", @@ -1594,6 +1606,9 @@ }, "RuntimeConfigure": { "$ref": "#/$defs/RuntimeConfigure" + }, + "DeviceOption": { + "$ref": "#/$defs/DeviceOption" } } } diff --git a/api/schema/v1.yaml b/api/schema/v1.yaml index b6c50c64b..e54211fe4 100644 --- a/api/schema/v1.yaml +++ b/api/schema/v1.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. # SPDX-License-Identifier: LGPL-3.0-or-later # # NOTE: @@ -1090,6 +1090,10 @@ $defs: type: object additionalProperties: type: string + device_mode: + type: array + items: + $ref: '#/$defs/DeviceOption' ext_defs: description: external extension definitions to extend the component type: object @@ -1097,6 +1101,9 @@ $defs: type: array items: $ref: '#/$defs/ExtensionDefine' + DeviceOption: + description: device option + enum: ["passthru"] type: object properties: # NOTE: "properties" is auto generated by referring all types is $defs diff --git a/apps/ll-cli/src/main.cpp b/apps/ll-cli/src/main.cpp index 4dcdd235f..d52842d23 100644 --- a/apps/ll-cli/src/main.cpp +++ b/apps/ll-cli/src/main.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -200,6 +201,13 @@ ll-cli run org.deepin.demo -- bash -x /path/to/bash/script)")); ->delimiter(',') ->allow_extra_args(false) ->group(""); + const std::map deviceOptionMap = { + { "passthru", linglong::api::types::v1::DeviceOption::Passthru }, + }; + cliRun->add_option("--device-mode", runOptions.deviceOptions, _("Add device options")) + ->delimiter(',') + ->transform(CLI::CheckedTransformer(deviceOptionMap, CLI::ignore_case)) + ->allow_extra_args(false); cliRun->add_option("COMMAND", runOptions.commands, _("Run commands in a running sandbox")); } diff --git a/configure.h.in b/configure.h.in index ed27e087d..b09056934 100644 --- a/configure.h.in +++ b/configure.h.in @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. + * SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. * * SPDX-License-Identifier: LGPL-3.0-or-later */ @@ -22,8 +22,9 @@ #define LINGLONG_CLIENT_NAME "@LINGLONG_CLI_BIN@" #define LINGLONG_CLIENT_PATH "@CMAKE_INSTALL_FULL_BINDIR@/@LINGLONG_CLI_BIN@" #define LINGLONG_EXPORT_PATH "@LINGLONG_EXPORT_PATH@" +#define LINGLONG_SYSCONFDIR SYSCONFDIR "/linglong" // The directory where the package install hooks reside. -#define LINGLONG_INSTALL_HOOKS_DIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@/linglong/config.d" +#define LINGLONG_INSTALL_HOOKS_DIR LINGLONG_SYSCONFDIR "/config.d" #define LINGLONG_EXPORT_VERSION "1.0.0.2" // The package's locale domain. diff --git a/libs/api/src/linglong/api/types/v1/DeviceOption.hpp b/libs/api/src/linglong/api/types/v1/DeviceOption.hpp new file mode 100644 index 000000000..387deece7 --- /dev/null +++ b/libs/api/src/linglong/api/types/v1/DeviceOption.hpp @@ -0,0 +1,39 @@ +// This file is generated by tools/codegen.sh +// DO NOT EDIT IT. + +// clang-format off + +// To parse this JSON data, first install +// +// json.hpp https://github.com/nlohmann/json +// +// Then include this file, and then do +// +// DeviceOption.hpp data = nlohmann::json::parse(jsonString); + +#pragma once + +#include +#include +#include "linglong/api/types/v1/helper.hpp" + +namespace linglong { +namespace api { +namespace types { +namespace v1 { +/** +* device option +*/ + +using nlohmann::json; + +/** +* device option +*/ +enum class DeviceOption : int { Passthru }; +} +} +} +} + +// clang-format on diff --git a/libs/api/src/linglong/api/types/v1/Generators.hpp b/libs/api/src/linglong/api/types/v1/Generators.hpp index 629c2b406..f21fd365f 100644 --- a/libs/api/src/linglong/api/types/v1/Generators.hpp +++ b/libs/api/src/linglong/api/types/v1/Generators.hpp @@ -59,6 +59,7 @@ #include "linglong/api/types/v1/ExportDirs.hpp" #include "linglong/api/types/v1/DialogMessage.hpp" #include "linglong/api/types/v1/DialogHandShakePayload.hpp" +#include "linglong/api/types/v1/DeviceOption.hpp" #include "linglong/api/types/v1/ContainerProcessStateInfo.hpp" #include "linglong/api/types/v1/CommonResult.hpp" #include "linglong/api/types/v1/CommonOptions.hpp" @@ -253,6 +254,9 @@ void to_json(json & j, const UpgradeListResult & x); void from_json(const json & j, LinglongAPIV1 & x); void to_json(json & j, const LinglongAPIV1 & x); +void from_json(const json & j, DeviceOption & x); +void to_json(json & j, const DeviceOption & x); + void from_json(const json & j, InteractionMessageType & x); void to_json(json & j, const InteractionMessageType & x); @@ -1200,12 +1204,16 @@ j["version"] = x.version; } inline void from_json(const json & j, RuntimeConfigure& x) { +x.deviceMode = get_stack_optional>(j, "device_mode"); x.env = get_stack_optional>(j, "env"); x.extDefs = get_stack_optional>>(j, "ext_defs"); } inline void to_json(json & j, const RuntimeConfigure & x) { j = json::object(); +if (x.deviceMode) { +j["device_mode"] = x.deviceMode; +} if (x.env) { j["env"] = x.env; } @@ -1283,6 +1291,7 @@ x.commonOptions = get_stack_optional(j, "CommonOptions"); x.commonResult = get_stack_optional(j, "CommonResult"); x.containerProcessStateInfo = get_stack_optional(j, "ContainerProcessStateInfo"); x.deviceNode = get_stack_optional(j, "DeviceNode"); +x.deviceOption = get_stack_optional(j, "DeviceOption"); x.dialogHandShakePayload = get_stack_optional(j, "DialogHandShakePayload"); x.dialogMessage = get_stack_optional(j, "DialogMessage"); x.exportDirs = get_stack_optional(j, "ExportDirs"); @@ -1354,6 +1363,9 @@ j["ContainerProcessStateInfo"] = x.containerProcessStateInfo; if (x.deviceNode) { j["DeviceNode"] = x.deviceNode; } +if (x.deviceOption) { +j["DeviceOption"] = x.deviceOption; +} if (x.dialogHandShakePayload) { j["DialogHandShakePayload"] = x.dialogHandShakePayload; } @@ -1467,6 +1479,18 @@ j["XDGDirectoryPermissions"] = x.xdgDirectoryPermissions; } } +inline void from_json(const json & j, DeviceOption & x) { +if (j == "passthru") x = DeviceOption::Passthru; +else { throw std::runtime_error("Input JSON does not conform to schema!"); } +} + +inline void to_json(json & j, const DeviceOption & x) { +switch (x) { +case DeviceOption::Passthru: j = "passthru"; break; +default: throw std::runtime_error("Unexpected value in enumeration \"DeviceOption\": " + std::to_string(static_cast(x))); +} +} + inline void from_json(const json & j, InteractionMessageType & x) { if (j == "Downgrade") x = InteractionMessageType::Downgrade; else if (j == "Install") x = InteractionMessageType::Install; diff --git a/libs/api/src/linglong/api/types/v1/LinglongAPIV1.hpp b/libs/api/src/linglong/api/types/v1/LinglongAPIV1.hpp index 4dd262617..be81901d1 100644 --- a/libs/api/src/linglong/api/types/v1/LinglongAPIV1.hpp +++ b/libs/api/src/linglong/api/types/v1/LinglongAPIV1.hpp @@ -65,6 +65,7 @@ namespace linglong { namespace api { namespace types { namespace v1 { +enum class DeviceOption : int; enum class InteractionMessageType : int; enum class State : int; } @@ -98,6 +99,7 @@ std::optional commonOptions; std::optional commonResult; std::optional containerProcessStateInfo; std::optional deviceNode; +std::optional deviceOption; std::optional dialogHandShakePayload; std::optional dialogMessage; std::optional exportDirs; diff --git a/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp b/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp index 4f1f710a4..f64cd8930 100644 --- a/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp +++ b/libs/api/src/linglong/api/types/v1/RuntimeConfigure.hpp @@ -19,6 +19,16 @@ #include "linglong/api/types/v1/ExtensionDefine.hpp" +namespace linglong { +namespace api { +namespace types { +namespace v1 { +enum class DeviceOption : int; +} +} +} +} + namespace linglong { namespace api { namespace types { @@ -26,6 +36,7 @@ namespace v1 { using nlohmann::json; struct RuntimeConfigure { +std::optional> deviceMode; std::optional> env; /** * external extension definitions to extend the component diff --git a/libs/common/src/linglong/common/dir.cpp b/libs/common/src/linglong/common/dir.cpp index 17d8e79e7..459d9dc14 100644 --- a/libs/common/src/linglong/common/dir.cpp +++ b/libs/common/src/linglong/common/dir.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -47,4 +47,9 @@ std::filesystem::path getUserRuntimeConfigDir() noexcept return configDir / "linglong"; } +std::filesystem::path getSystemRuntimeConfigDir() noexcept +{ + return LINGLONG_SYSCONFDIR; +} + } // namespace linglong::common::dir diff --git a/libs/common/src/linglong/common/dir.h b/libs/common/src/linglong/common/dir.h index 6eed5d4bc..998a0b139 100644 --- a/libs/common/src/linglong/common/dir.h +++ b/libs/common/src/linglong/common/dir.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -25,4 +25,6 @@ std::filesystem::path getUserCacheDir() noexcept; std::filesystem::path getUserRuntimeConfigDir() noexcept; +std::filesystem::path getSystemRuntimeConfigDir() noexcept; + } // namespace linglong::common::dir diff --git a/libs/linglong/src/linglong/cli/cli.cpp b/libs/linglong/src/linglong/cli/cli.cpp index 5307fd393..746d58216 100644 --- a/libs/linglong/src/linglong/cli/cli.cpp +++ b/libs/linglong/src/linglong/cli/cli.cpp @@ -625,7 +625,6 @@ int Cli::run(const RunOptions &options) .addUIdMapping(uid, uid, 1) .addGIdMapping(gid, gid, 1) .bindDefault() - .bindDevNode() .bindCgroup() .bindXDGRuntime() .bindUserGroup() @@ -640,6 +639,28 @@ int Cli::run(const RunOptions &options) .forwardDefaultEnv() .enableSelfAdjustingMount(); + bool devicePassthru = false; + if (runtimeConfig && runtimeConfig->deviceMode) { + for (const auto &option : *runtimeConfig->deviceMode) { + if (option == api::types::v1::DeviceOption::Passthru) { + devicePassthru = true; + break; + } + } + } + for (const auto &option : options.deviceOptions) { + if (option == api::types::v1::DeviceOption::Passthru) { + devicePassthru = true; + break; + } + } + + if (devicePassthru) { + cfgBuilder.bindDev(true); + } else { + cfgBuilder.bindDevNode(); + } + std::vector capabilities; // privileged mode shares host's user_namespace and add capabilities if (options.privileged) { diff --git a/libs/linglong/src/linglong/cli/cli.h b/libs/linglong/src/linglong/cli/cli.h index c4baddb6e..233306724 100644 --- a/libs/linglong/src/linglong/cli/cli.h +++ b/libs/linglong/src/linglong/cli/cli.h @@ -9,6 +9,7 @@ #include "linglong/api/dbus/v1/package_manager.h" #include "linglong/api/dbus/v1/task.h" #include "linglong/api/types/v1/CommonOptions.hpp" +#include "linglong/api/types/v1/DeviceOption.hpp" #include "linglong/api/types/v1/PackageInfoDisplay.hpp" #include "linglong/api/types/v1/RepositoryCacheLayersItem.hpp" #include "linglong/cli/interactive_notifier.h" @@ -58,6 +59,7 @@ struct RunOptions std::vector extensions; bool privileged{ false }; std::vector capsAdd; + std::vector deviceOptions; }; struct EnterOptions 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 3a58ba491..758419a54 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 @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -20,10 +20,10 @@ using namespace linglong::utils; TEST(RuntimeConfigTest, LoadFromPath) { TempDir tempDir; - // Create a test config file fs::path config_file = tempDir.path() / "test_config.json"; RuntimeConfigure config; + config.deviceMode = std::vector{ DeviceOption::Passthru }; config.env = std::map{ { "PATH", "/usr/bin" }, { "HOME", "/home/user" } }; @@ -36,17 +36,19 @@ TEST(RuntimeConfigTest, LoadFromPath) config.extDefs = std::map>{ { "test-app", { ext1 } } }; - // Write config to file std::ofstream file(config_file); nlohmann::json j; linglong::api::types::v1::to_json(j, config); file << j.dump(); file.close(); - // Load config from file auto loaded_config = linglong::utils::loadRuntimeConfig(config_file); ASSERT_TRUE(loaded_config.has_value()); + ASSERT_TRUE(loaded_config->deviceMode); + EXPECT_EQ(loaded_config->deviceMode->size(), 1); + EXPECT_EQ(loaded_config->deviceMode->at(0), DeviceOption::Passthru); + ASSERT_TRUE(loaded_config->env); EXPECT_EQ(loaded_config->env->size(), 2); EXPECT_EQ(loaded_config->env->at("PATH"), "/usr/bin"); @@ -68,8 +70,8 @@ TEST(RuntimeConfigTest, LoadFromPathNotExist) TEST(RuntimeConfigTest, MergeConfigs) { - // Create first config RuntimeConfigure config1; + config1.deviceMode = std::vector{ DeviceOption::Passthru }; config1.env = std::map{ { "PATH", "/usr/bin" }, { "HOME", "/home/user1" } }; @@ -79,8 +81,8 @@ TEST(RuntimeConfigTest, MergeConfigs) ext1.directory = "/opt/ext1"; config1.extDefs = std::map>{ { "app1", { ext1 } } }; - // Create second config RuntimeConfigure config2; + config2.deviceMode = std::vector{ DeviceOption::Passthru }; config2.env = std::map{ { "PATH", "/usr/local/bin" }, { "USER", "testuser" } }; @@ -88,32 +90,30 @@ TEST(RuntimeConfigTest, MergeConfigs) ext2.name = "extension2"; ext2.version = "2.0.0"; ext2.directory = "/opt/ext2"; - config2.extDefs = std::map>{ - { "app1", { ext2 } }, // Same app key as config1 - { "app2", { ext2 } } // Different app key - }; + config2.extDefs = std::map>{ { "app1", { ext2 } }, + { "app2", { ext2 } } }; - // Merge configs std::vector configs = { config1, config2 }; auto merged = linglong::utils::MergeRuntimeConfig(configs); - // Check environment variables + ASSERT_TRUE(merged.deviceMode); + EXPECT_EQ(merged.deviceMode->size(), 2); + EXPECT_EQ(merged.deviceMode->at(0), DeviceOption::Passthru); + EXPECT_EQ(merged.deviceMode->at(1), DeviceOption::Passthru); + ASSERT_TRUE(merged.env); - EXPECT_EQ(merged.env->size(), 3); // PATH, HOME, USER - EXPECT_EQ(merged.env->at("PATH"), "/usr/local/bin"); // config2 overrides config1 + EXPECT_EQ(merged.env->size(), 3); + EXPECT_EQ(merged.env->at("PATH"), "/usr/local/bin"); EXPECT_EQ(merged.env->at("HOME"), "/home/user1"); EXPECT_EQ(merged.env->at("USER"), "testuser"); - // Check extension definitions ASSERT_TRUE(merged.extDefs); - EXPECT_EQ(merged.extDefs->size(), 2); // app1, app2 + EXPECT_EQ(merged.extDefs->size(), 2); - // app1 should have both extensions EXPECT_EQ(merged.extDefs->at("app1").size(), 2); EXPECT_EQ(merged.extDefs->at("app1")[0].name, "extension1"); EXPECT_EQ(merged.extDefs->at("app1")[1].name, "extension2"); - // app2 should have only extension2 EXPECT_EQ(merged.extDefs->at("app2").size(), 1); EXPECT_EQ(merged.extDefs->at("app2")[0].name, "extension2"); } @@ -123,20 +123,19 @@ TEST(RuntimeConfigTest, MergeEmptyConfigs) std::vector empty_configs; auto merged = linglong::utils::MergeRuntimeConfig(empty_configs); + EXPECT_FALSE(merged.deviceMode); EXPECT_FALSE(merged.env); EXPECT_FALSE(merged.extDefs); } TEST(RuntimeConfigTest, MergePartialConfigs) { - // Config with only env RuntimeConfigure config1; + config1.deviceMode = std::vector{ DeviceOption::Passthru }; config1.env = std::map{ { "PATH", "/usr/bin" } }; - // Config empty RuntimeConfigure config2; - // Config with only extDefs RuntimeConfigure config3; ExtensionDefine ext; ext.name = "test-ext"; @@ -155,6 +154,10 @@ TEST(RuntimeConfigTest, MergePartialConfigs) EXPECT_EQ(merged.env->size(), 1); EXPECT_EQ(merged.env->at("PATH"), "/usr/bin"); + ASSERT_TRUE(merged.deviceMode); + EXPECT_EQ(merged.deviceMode->size(), 1); + EXPECT_EQ(merged.deviceMode->at(0), DeviceOption::Passthru); + ASSERT_TRUE(merged.extDefs); EXPECT_EQ(merged.extDefs->size(), 1); EXPECT_EQ(merged.extDefs->at("app").size(), 1); 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 3941e4f0b..9c5dcdcd6 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 @@ -211,27 +211,37 @@ ContainerCfgBuilder &ContainerCfgBuilder::bindProc() noexcept return *this; } -ContainerCfgBuilder &ContainerCfgBuilder::bindDev() noexcept -{ - devMount = { - Mount{ .destination = "/dev", - .options = string_list{ "nosuid", "strictatime", "mode=0755", "size=65536k" }, - .source = "tmpfs", - .type = "tmpfs" }, - Mount{ .destination = "/dev/pts", - .options = - string_list{ "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620" }, - .source = "devpts", - .type = "devpts" }, - Mount{ .destination = "/dev/shm", - .options = string_list{ "nosuid", "noexec", "nodev", "mode=1777" }, - .source = "shm", - .type = "tmpfs" }, - Mount{ .destination = "/dev/mqueue", - .options = string_list{ "rbind", "nosuid", "noexec", "nodev" }, - .source = "/dev/mqueue", - .type = "bind" }, - }; +ContainerCfgBuilder &ContainerCfgBuilder::bindDev(bool passthru) noexcept +{ + devPassthru = passthru; + if (devPassthru) { + devMount = { + Mount{ .destination = "/dev", + .options = string_list{ "rbind" }, + .source = "/dev", + .type = "bind" }, + }; + } else { + devMount = { + Mount{ .destination = "/dev", + .options = string_list{ "nosuid", "strictatime", "mode=0755", "size=65536k" }, + .source = "tmpfs", + .type = "tmpfs" }, + Mount{ .destination = "/dev/pts", + .options = + string_list{ "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620" }, + .source = "devpts", + .type = "devpts" }, + Mount{ .destination = "/dev/shm", + .options = string_list{ "nosuid", "noexec", "nodev", "mode=1777" }, + .source = "shm", + .type = "tmpfs" }, + Mount{ .destination = "/dev/mqueue", + .options = string_list{ "rbind", "nosuid", "noexec", "nodev" }, + .source = "/dev/mqueue", + .type = "bind" }, + }; + } return *this; } @@ -284,6 +294,8 @@ ContainerCfgBuilder &ContainerCfgBuilder::bindRun() noexcept .source = "tmpfs", .type = "tmpfs" } }; + bindIfExist(*runMount, "/run/udev"); + return *this; } @@ -1788,7 +1800,7 @@ bool ContainerCfgBuilder::mergeMount() noexcept std::move(devMount->begin(), devMount->end(), std::back_inserter(mounts)); } - if (devNodeMount) { + if (!devPassthru && devNodeMount) { std::move(devNodeMount->begin(), devNodeMount->end(), std::back_inserter(mounts)); } 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 0a3d78198..852b9873a 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 @@ -123,7 +123,7 @@ class ContainerCfgBuilder ContainerCfgBuilder &bindDefault() noexcept; ContainerCfgBuilder &bindSys() noexcept; ContainerCfgBuilder &bindProc() noexcept; - ContainerCfgBuilder &bindDev() noexcept; + ContainerCfgBuilder &bindDev(bool passthru = false) noexcept; ContainerCfgBuilder & bindDevNode(std::function ifBind = nullptr) noexcept; ContainerCfgBuilder &bindCgroup() noexcept; @@ -332,6 +332,7 @@ class ContainerCfgBuilder bool disableGenerateContainerInfo{ true }; bool applyPatchEnabled = true; bool isolateTmp{ false }; + bool devPassthru{ false }; // display system std::optional waylandSocket; diff --git a/libs/utils/src/linglong/utils/runtime_config.cpp b/libs/utils/src/linglong/utils/runtime_config.cpp index 70e966522..41c2b0901 100644 --- a/libs/utils/src/linglong/utils/runtime_config.cpp +++ b/libs/utils/src/linglong/utils/runtime_config.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. + * SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. * * SPDX-License-Identifier: LGPL-3.0-or-later */ @@ -18,16 +18,8 @@ using RuntimeConfigure = linglong::api::types::v1::RuntimeConfigure; namespace { linglong::utils::error::Result> -loadUserRuntimeConfig(const std::string &appId) +loadRuntimeConfigFromDir(const std::filesystem::path &configDir, const std::string &appId) { - LINGLONG_TRACE( - fmt::format("load user runtime config for {}", appId.empty() ? "global" : appId)); - - auto configDir = linglong::common::dir::getUserRuntimeConfigDir(); - if (configDir.empty()) { - return std::nullopt; - } - std::filesystem::path configPath; if (appId.empty()) { configPath = configDir / "config.json"; @@ -40,12 +32,30 @@ loadUserRuntimeConfig(const std::string &appId) return std::nullopt; } - auto result = linglong::utils::loadRuntimeConfig(configPath); - if (!result) { - return LINGLONG_ERR(result); + return linglong::utils::loadRuntimeConfig(configPath); +} + +linglong::utils::error::Result> +loadUserRuntimeConfig(const std::string &appId) +{ + LINGLONG_TRACE( + fmt::format("load user runtime config for {}", appId.empty() ? "global" : appId)); + + auto configDir = linglong::common::dir::getUserRuntimeConfigDir(); + if (configDir.empty()) { + return std::nullopt; } - return std::move(result).value(); + return loadRuntimeConfigFromDir(configDir, appId); +} + +linglong::utils::error::Result> +loadSystemRuntimeConfig(const std::string &appId) +{ + LINGLONG_TRACE( + fmt::format("load system runtime config for {}", appId.empty() ? "global" : appId)); + + return loadRuntimeConfigFromDir(linglong::common::dir::getSystemRuntimeConfigDir(), appId); } } // namespace @@ -57,7 +67,16 @@ RuntimeConfigure MergeRuntimeConfig(const std::vector &configs RuntimeConfigure result; for (const auto &config : configs) { - // Merge environment variables + if (config.deviceMode) { + if (!result.deviceMode) { + result.deviceMode = config.deviceMode; + } else { + result.deviceMode->insert(result.deviceMode->end(), + config.deviceMode->begin(), + config.deviceMode->end()); + } + } + if (config.env) { if (!result.env) { result.env = config.env; @@ -68,7 +87,6 @@ RuntimeConfigure MergeRuntimeConfig(const std::vector &configs } } - // Merge extension definitions if (config.extDefs) { if (!result.extDefs) { result.extDefs = config.extDefs; @@ -98,30 +116,45 @@ loadRuntimeConfig(const std::filesystem::path &path) return LINGLONG_ERR("failed to load runtime config", result); } - return std::move(result).value(); + return result; } utils::error::Result> loadRuntimeConfig(const std::string &appId) { LINGLONG_TRACE("load runtime config for app: " + appId); - // Merge configs in order: user global -> user app std::vector configs; + auto systemGlobal = loadSystemRuntimeConfig(""); + if (!systemGlobal) { + return LINGLONG_ERR(systemGlobal); + } + if (systemGlobal->has_value()) { + configs.emplace_back(std::move(systemGlobal->value())); + } + + auto systemApp = loadSystemRuntimeConfig(appId); + if (!systemApp) { + return LINGLONG_ERR(systemApp); + } + if (systemApp->has_value()) { + configs.emplace_back(std::move(systemApp->value())); + } + auto userGlobal = loadUserRuntimeConfig(""); if (!userGlobal) { return LINGLONG_ERR(userGlobal); } - if ((*userGlobal).has_value()) { - configs.emplace_back((*userGlobal).value()); + if (userGlobal->has_value()) { + configs.emplace_back(std::move(userGlobal->value())); } auto userApp = loadUserRuntimeConfig(appId); if (!userApp) { return LINGLONG_ERR(userApp); } - if ((*userApp).has_value()) { - configs.emplace_back((*userApp).value()); + if (userApp->has_value()) { + configs.emplace_back(std::move(userApp->value())); } if (configs.empty()) {