mirror of https://github.com/linuxdeepin/linglong
Compare commits
8 Commits
3d7aff57d2
...
3f1e1fce25
| Author | SHA1 | Date |
|---|---|---|
|
|
3f1e1fce25 | |
|
|
41a2650979 | |
|
|
c31b6d74cf | |
|
|
45f13e1813 | |
|
|
bf24e8397e | |
|
|
ef0a7539ca | |
|
|
c40805bf34 | |
|
|
3213d097af |
|
|
@ -105,6 +105,118 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"LlCliFilesystemEntry": {
|
||||
"title": "LlCliFilesystemEntry",
|
||||
"description": "filesystem entry used by ll-cli configuration",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"host",
|
||||
"target"
|
||||
],
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "source path on host"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "mount target path inside container"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "mount mode, either ro or rw"
|
||||
},
|
||||
"persist": {
|
||||
"type": "boolean",
|
||||
"description": "whether this mount should persist in sandbox storage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LlCliCommandConfig": {
|
||||
"title": "LlCliCommandConfig",
|
||||
"description": "per-command overrides for ll-cli configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "environment variables applied to command",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filesystem": {
|
||||
"type": "array",
|
||||
"description": "filesystem entries applied to command",
|
||||
"items": {
|
||||
"$ref": "#/$defs/LlCliFilesystemEntry"
|
||||
}
|
||||
},
|
||||
"args_prefix": {
|
||||
"type": "array",
|
||||
"description": "arguments prepended before command",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"args_suffix": {
|
||||
"type": "array",
|
||||
"description": "arguments appended after command",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "string",
|
||||
"description": "override entrypoint for command"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"description": "working directory for command"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LlCliConfig": {
|
||||
"title": "LlCliConfig",
|
||||
"description": "default ll-cli configuration provided by package",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filesystem_allow_only": {
|
||||
"type": "array",
|
||||
"description": "explicit mount allowlist; when present only these mounts are permitted",
|
||||
"items": {
|
||||
"$ref": "#/$defs/LlCliFilesystemEntry"
|
||||
}
|
||||
},
|
||||
"filesystem": {
|
||||
"type": "array",
|
||||
"description": "additional filesystem mounts appended to defaults",
|
||||
"items": {
|
||||
"$ref": "#/$defs/LlCliFilesystemEntry"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"description": "default environment variables",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"type": "object",
|
||||
"description": "command specific overrides",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/LlCliCommandConfig"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"description": "default extensions applied when launching app",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ContainerProcessStateInfo": {
|
||||
"description": "information about process which in container that start by ll-cli, all content\nwill write to /run/linglong/UID/PID\n",
|
||||
"type": "object",
|
||||
|
|
@ -379,6 +491,10 @@
|
|||
"permissions": {
|
||||
"$ref": "#/$defs/ApplicationConfigurationPermissions"
|
||||
},
|
||||
"cli_config": {
|
||||
"$ref": "#/$defs/LlCliConfig",
|
||||
"description": "default ll-cli configuration delivered with package"
|
||||
},
|
||||
"runtime": {
|
||||
"type": "string",
|
||||
"description": "used runtime of package"
|
||||
|
|
@ -773,6 +889,10 @@
|
|||
"$ref": "#/$defs/ExtensionDefine"
|
||||
}
|
||||
},
|
||||
"cli_config": {
|
||||
"$ref": "#/$defs/LlCliConfig",
|
||||
"description": "default ll-cli configuration provided by package"
|
||||
},
|
||||
"ext_impl": {
|
||||
"$ref": "#/$defs/ExtensionImpl"
|
||||
}
|
||||
|
|
@ -1418,6 +1538,15 @@
|
|||
"ApplicationConfigurationPermissions": {
|
||||
"$ref": "#/$defs/ApplicationConfigurationPermissions"
|
||||
},
|
||||
"LlCliFilesystemEntry": {
|
||||
"$ref": "#/$defs/LlCliFilesystemEntry"
|
||||
},
|
||||
"LlCliCommandConfig": {
|
||||
"$ref": "#/$defs/LlCliCommandConfig"
|
||||
},
|
||||
"LlCliConfig": {
|
||||
"$ref": "#/$defs/LlCliConfig"
|
||||
},
|
||||
"ContainerProcessStateInfo": {
|
||||
"$ref": "#/$defs/ContainerProcessStateInfo"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -95,6 +95,87 @@ $defs:
|
|||
destination:
|
||||
type: string
|
||||
description: mount source file to the another position of container
|
||||
LlCliFilesystemEntry:
|
||||
title: LlCliFilesystemEntry
|
||||
description: filesystem entry used by ll-cli configuration
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
- target
|
||||
properties:
|
||||
host:
|
||||
type: string
|
||||
description: source path on host
|
||||
target:
|
||||
type: string
|
||||
description: mount target path inside container
|
||||
mode:
|
||||
type: string
|
||||
description: mount mode, either ro or rw
|
||||
persist:
|
||||
type: boolean
|
||||
description: whether this mount should persist in sandbox storage
|
||||
LlCliCommandConfig:
|
||||
title: LlCliCommandConfig
|
||||
description: per-command overrides for ll-cli configuration
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
type: object
|
||||
description: environment variables applied to command
|
||||
additionalProperties:
|
||||
type: string
|
||||
filesystem:
|
||||
type: array
|
||||
description: filesystem entries applied to command
|
||||
items:
|
||||
$ref: '#/$defs/LlCliFilesystemEntry'
|
||||
args_prefix:
|
||||
type: array
|
||||
description: arguments prepended before command
|
||||
items:
|
||||
type: string
|
||||
args_suffix:
|
||||
type: array
|
||||
description: arguments appended after command
|
||||
items:
|
||||
type: string
|
||||
entrypoint:
|
||||
type: string
|
||||
description: override entrypoint for command
|
||||
cwd:
|
||||
type: string
|
||||
description: working directory for command
|
||||
LlCliConfig:
|
||||
title: LlCliConfig
|
||||
description: default ll-cli configuration provided by package
|
||||
type: object
|
||||
properties:
|
||||
filesystem_allow_only:
|
||||
type: array
|
||||
description: explicit mount allowlist; when present only these mounts are permitted
|
||||
items:
|
||||
$ref: '#/$defs/LlCliFilesystemEntry'
|
||||
filesystem:
|
||||
type: array
|
||||
description: additional filesystem mounts appended to defaults
|
||||
items:
|
||||
$ref: '#/$defs/LlCliFilesystemEntry'
|
||||
env:
|
||||
type: object
|
||||
description: default environment variables
|
||||
additionalProperties:
|
||||
type: string
|
||||
commands:
|
||||
type: object
|
||||
description: command specific overrides
|
||||
additionalProperties:
|
||||
$ref: '#/$defs/LlCliCommandConfig'
|
||||
extensions:
|
||||
type: array
|
||||
description: default extensions applied when launching app
|
||||
items:
|
||||
type: string
|
||||
ContainerProcessStateInfo:
|
||||
description: |
|
||||
information about process which in container that start by ll-cli, all content
|
||||
|
|
@ -312,6 +393,9 @@ $defs:
|
|||
type: string
|
||||
permissions:
|
||||
$ref: '#/$defs/ApplicationConfigurationPermissions'
|
||||
cli_config:
|
||||
$ref: '#/$defs/LlCliConfig'
|
||||
description: default ll-cli configuration delivered with package
|
||||
runtime:
|
||||
type: string
|
||||
description: used runtime of package
|
||||
|
|
@ -604,6 +688,9 @@ $defs:
|
|||
description: description of extension
|
||||
items:
|
||||
$ref: '#/$defs/ExtensionDefine'
|
||||
cli_config:
|
||||
$ref: '#/$defs/LlCliConfig'
|
||||
description: default ll-cli configuration provided by package
|
||||
ext_impl:
|
||||
$ref: '#/$defs/ExtensionImpl'
|
||||
PackageInfo:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
#include "configure.h"
|
||||
#include "linglong/api/dbus/v1/dbus_peer.h"
|
||||
#include "linglong/api/types/v1/Generators.hpp"
|
||||
#include "linglong/api/types/v1/PackageInfoV2.hpp"
|
||||
#include "linglong/cli/cli.h"
|
||||
#include "linglong/cli/cli_printer.h"
|
||||
#include "linglong/cli/dbus_notifier.h"
|
||||
|
|
@ -33,10 +35,20 @@
|
|||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <wordexp.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
using namespace linglong::utils::error;
|
||||
using namespace linglong::package;
|
||||
using namespace linglong::cli;
|
||||
|
|
@ -631,22 +643,887 @@ linglong::utils::error::Result<linglong::repo::OSTreeRepo *> initOSTreeRepo()
|
|||
return repo;
|
||||
}
|
||||
|
||||
|
||||
// ===== begin: ll-cli config helpers =====
|
||||
using json = nlohmann::json;
|
||||
|
||||
static std::string configUsageLines()
|
||||
{
|
||||
return std::string{
|
||||
_(" ll-cli config set-extensions [--global | <appid> | --base <baseid>] ext1,ext2\n"
|
||||
" ll-cli config add-extensions [--global | <appid> | --base <baseid>] ext1,ext2\n"
|
||||
" ll-cli config set-env [--global | <appid> | --base <baseid>] KEY=VAL [KEY=VAL ...]\n"
|
||||
" ll-cli config unset-env [--global | <appid> | --base <baseid>] KEY [KEY ...]\n"
|
||||
" ll-cli config add-fs [--global | <appid> | --base <baseid>] --host PATH --target PATH [--mode "
|
||||
"ro|rw] [--persist]\n"
|
||||
" ll-cli config rm-fs [--global | <appid> | --base <baseid>] (--target PATH | --index N)\n"
|
||||
" ll-cli config add-fs-allow [--global | <appid> | --base <baseid>] --host PATH --target PATH [--mode "
|
||||
"ro|rw] [--persist]\n"
|
||||
" ll-cli config rm-fs-allow [--global | <appid> | --base <baseid>] (--target PATH | --index N)\n"
|
||||
" ll-cli config clear-fs-allow [--global | <appid> | --base <baseid>]\n"
|
||||
" ll-cli config set-command [--global | <appid> | --base <baseid>] <cmd> [--entrypoint P] [--cwd D] "
|
||||
"[--args-prefix \"...\"] [--args-suffix \"...\"] [KEY=VAL ...]\n"
|
||||
" ll-cli config unset-command [--global | <appid> | --base <baseid>] <cmd>\n") };
|
||||
}
|
||||
|
||||
static std::string configShortHelp()
|
||||
{
|
||||
return std::string{ _("Configuration commands:\n"
|
||||
" config Manage ll-cli configuration (see `ll-cli config --help`)\n") };
|
||||
}
|
||||
|
||||
static std::string configFooterMessage()
|
||||
{
|
||||
return std::string{ _("If you found any problems during use,\n"
|
||||
"You can report bugs to the linyaps team under this project: "
|
||||
"https://github.com/OpenAtom-Linyaps/linyaps/issues") };
|
||||
}
|
||||
|
||||
static void printConfigUsage(FILE *stream = stderr)
|
||||
{
|
||||
auto usageLines = configUsageLines();
|
||||
std::fprintf(stream, "%s\n%s", _("Usage:"), usageLines.c_str());
|
||||
}
|
||||
|
||||
enum class Scope { Global, App, Base };
|
||||
|
||||
static std::filesystem::path getBaseConfigDir()
|
||||
{
|
||||
const char *xdg = ::getenv("XDG_CONFIG_HOME");
|
||||
if (xdg && xdg[0]) {
|
||||
return std::filesystem::path(xdg) / "linglong";
|
||||
}
|
||||
const char *home = ::getenv("HOME");
|
||||
if (home && home[0]) {
|
||||
return std::filesystem::path(home) / ".config" / "linglong";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::filesystem::path getSystemConfigDir()
|
||||
{
|
||||
return std::filesystem::path(LINGLONG_DATA_DIR) / "config";
|
||||
}
|
||||
|
||||
static std::filesystem::path buildConfigPath(const std::filesystem::path &base,
|
||||
Scope scope,
|
||||
const std::string &appId,
|
||||
const std::string &baseId)
|
||||
{
|
||||
if (base.empty()) {
|
||||
return {};
|
||||
}
|
||||
switch (scope) {
|
||||
case Scope::Global:
|
||||
return base / "config.json";
|
||||
case Scope::App:
|
||||
return base / "apps" / appId / "config.json";
|
||||
case Scope::Base:
|
||||
return base / "base" / baseId / "config.json";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::filesystem::path getConfigPath(Scope scope,
|
||||
const std::string &appId,
|
||||
const std::string &baseId)
|
||||
{
|
||||
return buildConfigPath(getBaseConfigDir(), scope, appId, baseId);
|
||||
}
|
||||
|
||||
static std::vector<std::filesystem::path> getConfigSearchPaths(Scope scope,
|
||||
const std::string &appId,
|
||||
const std::string &baseId)
|
||||
{
|
||||
std::vector<std::filesystem::path> paths;
|
||||
std::unordered_set<std::string> seen;
|
||||
auto addPath = [&](const std::filesystem::path &candidate) {
|
||||
if (candidate.empty()) {
|
||||
return;
|
||||
}
|
||||
auto normalized = candidate.lexically_normal();
|
||||
auto key = normalized.string();
|
||||
if (!key.empty() && seen.insert(key).second) {
|
||||
paths.emplace_back(std::move(normalized));
|
||||
}
|
||||
};
|
||||
|
||||
addPath(buildConfigPath(getBaseConfigDir(), scope, appId, baseId));
|
||||
addPath(buildConfigPath(getSystemConfigDir(), scope, appId, baseId));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static bool ensureParentDir(const std::filesystem::path &p)
|
||||
{
|
||||
std::error_code ec;
|
||||
auto parent = p.parent_path();
|
||||
if (parent.empty()) {
|
||||
return true;
|
||||
}
|
||||
return std::filesystem::create_directories(parent, ec) || std::filesystem::exists(parent);
|
||||
}
|
||||
|
||||
static std::optional<json> readJsonIfExists(const std::filesystem::path &p, bool *existed = nullptr)
|
||||
{
|
||||
try {
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(p, ec)) {
|
||||
if (existed) {
|
||||
*existed = false;
|
||||
}
|
||||
return json::object();
|
||||
}
|
||||
std::ifstream in(p);
|
||||
if (!in.is_open()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (existed) {
|
||||
*existed = true;
|
||||
}
|
||||
json j;
|
||||
in >> j;
|
||||
return j.is_null() ? json::object() : j;
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static bool writeJsonAtomic(const std::filesystem::path &p, const json &j)
|
||||
{
|
||||
try {
|
||||
if (!ensureParentDir(p)) {
|
||||
return false;
|
||||
}
|
||||
auto tmp = p;
|
||||
tmp += ".tmp";
|
||||
{
|
||||
std::ofstream out(tmp);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
out << j.dump(2) << "\n";
|
||||
}
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(tmp, p, ec);
|
||||
if (ec) {
|
||||
std::filesystem::remove(p, ec);
|
||||
std::filesystem::rename(tmp, p, ec);
|
||||
}
|
||||
return !ec;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::string> splitCsv(const std::string &s)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
std::stringstream ss(s);
|
||||
std::string tok;
|
||||
while (std::getline(ss, tok, ',')) {
|
||||
if (!tok.empty()) {
|
||||
out.push_back(tok);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string trim(const std::string &s)
|
||||
{
|
||||
const char *ws = " \t\r\n";
|
||||
size_t b = s.find_first_not_of(ws);
|
||||
if (b == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
size_t e = s.find_last_not_of(ws);
|
||||
return s.substr(b, e - b + 1);
|
||||
}
|
||||
|
||||
static void jsonSetExtensions(json &root, const std::vector<std::string> &exts, bool overwrite)
|
||||
{
|
||||
auto &arr = root["extensions"];
|
||||
if (!arr.is_array() || overwrite) {
|
||||
arr = json::array();
|
||||
}
|
||||
std::unordered_set<std::string> exist;
|
||||
if (arr.is_array()) {
|
||||
for (auto &e : arr) {
|
||||
if (e.is_string()) {
|
||||
exist.insert(e.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &s : exts) {
|
||||
if (!s.empty() && !exist.count(s)) {
|
||||
arr.push_back(s);
|
||||
exist.insert(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void jsonSetEnv(json &root, const std::vector<std::string> &kvs)
|
||||
{
|
||||
auto &obj = root["env"];
|
||||
if (!obj.is_object()) {
|
||||
obj = json::object();
|
||||
}
|
||||
for (auto &kv : kvs) {
|
||||
auto pos = kv.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
auto k = trim(kv.substr(0, pos));
|
||||
auto v = kv.substr(pos + 1);
|
||||
if (k.empty()) {
|
||||
continue;
|
||||
}
|
||||
obj[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
static void jsonUnsetEnv(json &root, const std::vector<std::string> &keys)
|
||||
{
|
||||
if (!root.contains("env") || !root["env"].is_object()) {
|
||||
return;
|
||||
}
|
||||
for (auto &k : keys) {
|
||||
root["env"].erase(k);
|
||||
}
|
||||
}
|
||||
|
||||
struct FsArg {
|
||||
std::string host, target, mode;
|
||||
bool persist = false;
|
||||
};
|
||||
|
||||
static void jsonAddFsTo(json &root, const FsArg &fs, const char *field)
|
||||
{
|
||||
auto &arr = root[field];
|
||||
if (!arr.is_array()) {
|
||||
arr = json::array();
|
||||
}
|
||||
for (auto &e : arr) {
|
||||
if (!e.is_object()) {
|
||||
continue;
|
||||
}
|
||||
if (e.value("host", "") == fs.host && e.value("target", "") == fs.target) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
json o = json::object();
|
||||
o["host"] = fs.host;
|
||||
o["target"] = fs.target;
|
||||
o["mode"] = (fs.mode == "rw" ? "rw" : "ro");
|
||||
if (fs.persist) {
|
||||
o["persist"] = true;
|
||||
}
|
||||
arr.push_back(std::move(o));
|
||||
}
|
||||
|
||||
static void jsonAddFs(json &root, const FsArg &fs)
|
||||
{
|
||||
jsonAddFsTo(root, fs, "filesystem");
|
||||
}
|
||||
|
||||
static void jsonAddFsAllow(json &root, const FsArg &fs)
|
||||
{
|
||||
jsonAddFsTo(root, fs, "filesystem_allow_only");
|
||||
}
|
||||
|
||||
static bool jsonRmFsByTargetFrom(json &root, const std::string &target, const char *field)
|
||||
{
|
||||
if (!root.contains(field) || !root[field].is_array()) {
|
||||
return false;
|
||||
}
|
||||
auto &arr = root[field];
|
||||
auto old = arr.size();
|
||||
arr.erase(std::remove_if(arr.begin(), arr.end(), [&](const json &e) {
|
||||
return e.is_object() && e.value("target", "") == target;
|
||||
}),
|
||||
arr.end());
|
||||
return arr.size() != old;
|
||||
}
|
||||
|
||||
static bool jsonRmFsByTarget(json &root, const std::string &target)
|
||||
{
|
||||
return jsonRmFsByTargetFrom(root, target, "filesystem");
|
||||
}
|
||||
|
||||
static bool jsonRmFsAllowByTarget(json &root, const std::string &target)
|
||||
{
|
||||
return jsonRmFsByTargetFrom(root, target, "filesystem_allow_only");
|
||||
}
|
||||
|
||||
static bool jsonRmFsByIndexFrom(json &root, size_t idx, const char *field)
|
||||
{
|
||||
if (!root.contains(field) || !root[field].is_array()) {
|
||||
return false;
|
||||
}
|
||||
auto &arr = root[field];
|
||||
if (idx >= arr.size()) {
|
||||
return false;
|
||||
}
|
||||
arr.erase(arr.begin() + idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool jsonRmFsByIndex(json &root, size_t idx)
|
||||
{
|
||||
return jsonRmFsByIndexFrom(root, idx, "filesystem");
|
||||
}
|
||||
|
||||
static bool jsonRmFsAllowByIndex(json &root, size_t idx)
|
||||
{
|
||||
return jsonRmFsByIndexFrom(root, idx, "filesystem_allow_only");
|
||||
}
|
||||
|
||||
static void jsonClearFsAllow(json &root)
|
||||
{
|
||||
root["filesystem_allow_only"] = json::array();
|
||||
}
|
||||
|
||||
struct CmdSetArg {
|
||||
std::string cmd;
|
||||
std::optional<std::string> entrypoint;
|
||||
std::optional<std::string> cwd;
|
||||
std::vector<std::string> argsPrefix;
|
||||
std::vector<std::string> argsSuffix;
|
||||
std::vector<std::string> envKVs;
|
||||
};
|
||||
|
||||
static void jsonSetCommand(json &root, const CmdSetArg &a)
|
||||
{
|
||||
if (a.cmd.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &cmds = root["commands"];
|
||||
if (!cmds.is_object()) {
|
||||
cmds = json::object();
|
||||
}
|
||||
auto &node = cmds[a.cmd];
|
||||
if (!node.is_object()) {
|
||||
node = json::object();
|
||||
}
|
||||
if (a.entrypoint) {
|
||||
node["entrypoint"] = *a.entrypoint;
|
||||
}
|
||||
if (a.cwd) {
|
||||
node["cwd"] = *a.cwd;
|
||||
}
|
||||
if (!a.argsPrefix.empty()) {
|
||||
auto &arr = node["args_prefix"];
|
||||
arr = json::array();
|
||||
for (auto &s : a.argsPrefix) {
|
||||
arr.push_back(s);
|
||||
}
|
||||
}
|
||||
if (!a.argsSuffix.empty()) {
|
||||
auto &arr = node["args_suffix"];
|
||||
arr = json::array();
|
||||
for (auto &s : a.argsSuffix) {
|
||||
arr.push_back(s);
|
||||
}
|
||||
}
|
||||
if (!a.envKVs.empty()) {
|
||||
auto &env = node["env"];
|
||||
if (!env.is_object()) {
|
||||
env = json::object();
|
||||
}
|
||||
for (auto &kv : a.envKVs) {
|
||||
auto pos = kv.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
auto k = trim(kv.substr(0, pos));
|
||||
auto v = kv.substr(pos + 1);
|
||||
if (!k.empty()) {
|
||||
env[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void jsonUnsetCommand(json &root, const std::string &cmd)
|
||||
{
|
||||
if (!root.contains("commands") || !root["commands"].is_object()) {
|
||||
return;
|
||||
}
|
||||
root["commands"].erase(cmd);
|
||||
}
|
||||
// ===== end: ll-cli config helpers =====
|
||||
|
||||
class ConfigAwareFormatter : public CLI::Formatter {
|
||||
public:
|
||||
ConfigAwareFormatter(std::string shortSection, std::string fullSection, std::string footerMessage)
|
||||
: shortSection_(std::move(shortSection))
|
||||
, fullSection_(std::move(fullSection))
|
||||
, footerMessage_(std::move(footerMessage))
|
||||
{}
|
||||
|
||||
std::string make_help(const CLI::App *app, std::string name, CLI::AppFormatMode mode) const override
|
||||
{
|
||||
std::string result = Formatter::make_help(app, std::move(name), mode);
|
||||
|
||||
const std::string §ion = (mode == CLI::AppFormatMode::All) ? fullSection_ : shortSection_;
|
||||
if (!section.empty()) {
|
||||
if (!result.empty() && result.back() != '\n') {
|
||||
result.push_back('\n');
|
||||
}
|
||||
result += section;
|
||||
if (!section.empty() && result.back() != '\n') {
|
||||
result.push_back('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (!footerMessage_.empty()) {
|
||||
if (!result.empty() && result.back() != '\n') {
|
||||
result.push_back('\n');
|
||||
}
|
||||
result += footerMessage_;
|
||||
if (result.back() != '\n') {
|
||||
result.push_back('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string shortSection_;
|
||||
std::string fullSection_;
|
||||
std::string footerMessage_;
|
||||
};
|
||||
|
||||
int runCliApplication(int argc, char **mainArgv)
|
||||
{
|
||||
// ===== begin: "ll-cli config ..." dispatcher =====
|
||||
{
|
||||
if (argc >= 2 && std::string(mainArgv[1]) == "config") {
|
||||
if (argc < 3) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
std::string sub = mainArgv[2];
|
||||
|
||||
auto parseScope = [&](int start) -> std::tuple<Scope, std::string, std::string, int> {
|
||||
if (start >= argc) {
|
||||
return { Scope::Global, "", "", start };
|
||||
}
|
||||
std::string t = mainArgv[start];
|
||||
if (t == "--global") {
|
||||
return { Scope::Global, "", "", start + 1 };
|
||||
}
|
||||
if (t == "--base") {
|
||||
if (start + 1 >= argc) {
|
||||
fprintf(stderr, "--base requires <baseid>\n");
|
||||
return { Scope::Global, "", "", argc };
|
||||
}
|
||||
return { Scope::Base, "", mainArgv[start + 1], start + 2 };
|
||||
}
|
||||
return { Scope::App, t, "", start + 1 };
|
||||
};
|
||||
|
||||
auto loadCliConfigFromPackage = [&](Scope scope,
|
||||
const std::string &appId,
|
||||
const std::string &baseId)
|
||||
-> std::optional<json> {
|
||||
if (scope == Scope::Global) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto repoResult = initOSTreeRepo();
|
||||
if (!repoResult) {
|
||||
qWarning() << "load cli config from package failed:" << repoResult.error();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto *repoPtr = *repoResult;
|
||||
auto list = repoPtr->listLocal();
|
||||
if (!list) {
|
||||
qWarning() << "list local packages failed:" << list.error();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto matcher = [&](const linglong::api::types::v1::PackageInfoV2 &info) -> bool {
|
||||
if (scope == Scope::App) {
|
||||
return info.id == appId && info.kind == "app";
|
||||
}
|
||||
if (scope == Scope::Base) {
|
||||
return info.id == baseId && info.kind == "base";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto it = std::find_if(list->begin(), list->end(), matcher);
|
||||
if (it == list->end() || !it->cliConfig) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
json packaged = *(it->cliConfig);
|
||||
return packaged;
|
||||
};
|
||||
|
||||
auto openConfig = [&](Scope scope,
|
||||
const std::string &appId,
|
||||
const std::string &baseId) -> std::optional<json> {
|
||||
auto userPath = getConfigPath(scope, appId, baseId);
|
||||
if (userPath.empty()) {
|
||||
fprintf(stderr, "invalid config path\n");
|
||||
return std::nullopt;
|
||||
}
|
||||
bool userExists = false;
|
||||
auto userJson = readJsonIfExists(userPath, &userExists);
|
||||
if (!userJson) {
|
||||
fprintf(stderr, "failed to read %s\n", userPath.string().c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!userExists) {
|
||||
auto searchPaths = getConfigSearchPaths(scope, appId, baseId);
|
||||
for (size_t idx = 1; idx < searchPaths.size(); ++idx) {
|
||||
bool existed = false;
|
||||
auto fallback = readJsonIfExists(searchPaths[idx], &existed);
|
||||
if (!fallback || !existed) {
|
||||
continue;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
if (auto packaged = loadCliConfigFromPackage(scope, appId, baseId)) {
|
||||
return packaged;
|
||||
}
|
||||
}
|
||||
return userJson;
|
||||
};
|
||||
auto saveConfig = [&](Scope scope,
|
||||
const std::string &appId,
|
||||
const std::string &baseId,
|
||||
const json &j) -> bool {
|
||||
auto path = getConfigPath(scope, appId, baseId);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!writeJsonAtomic(path, j)) {
|
||||
fprintf(stderr, "failed to write %s\n", path.string().c_str());
|
||||
return false;
|
||||
}
|
||||
printf("Written %s\n", path.string().c_str());
|
||||
return true;
|
||||
};
|
||||
|
||||
if (sub == "set-extensions" || sub == "add-extensions") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
if (i >= argc) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> exts = splitCsv(mainArgv[i]);
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonSetExtensions(*j, exts, sub == "set-extensions");
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "set-env") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
if (i >= argc) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> kvs;
|
||||
for (; i < argc; ++i) {
|
||||
kvs.push_back(mainArgv[i]);
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonSetEnv(*j, kvs);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "unset-env") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
if (i >= argc) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> keys;
|
||||
for (; i < argc; ++i) {
|
||||
keys.push_back(mainArgv[i]);
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonUnsetEnv(*j, keys);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "add-fs") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
FsArg fs;
|
||||
fs.mode = "ro";
|
||||
fs.persist = false;
|
||||
for (; i < argc; ++i) {
|
||||
std::string a = mainArgv[i];
|
||||
if (a == "--persist") {
|
||||
fs.persist = true;
|
||||
} else if (a == "--host" && i + 1 < argc) {
|
||||
fs.host = mainArgv[++i];
|
||||
} else if (a == "--target" && i + 1 < argc) {
|
||||
fs.target = mainArgv[++i];
|
||||
} else if (a == "--mode" && i + 1 < argc) {
|
||||
fs.mode = mainArgv[++i];
|
||||
} else {
|
||||
fprintf(stderr, "unknown arg: %s\n", a.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (fs.host.empty() || fs.target.empty()) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonAddFs(*j, fs);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "add-fs-allow") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
FsArg fs;
|
||||
fs.mode = "ro";
|
||||
fs.persist = false;
|
||||
for (; i < argc; ++i) {
|
||||
std::string a = mainArgv[i];
|
||||
if (a == "--persist") {
|
||||
fs.persist = true;
|
||||
} else if (a == "--host" && i + 1 < argc) {
|
||||
fs.host = mainArgv[++i];
|
||||
} else if (a == "--target" && i + 1 < argc) {
|
||||
fs.target = mainArgv[++i];
|
||||
} else if (a == "--mode" && i + 1 < argc) {
|
||||
fs.mode = mainArgv[++i];
|
||||
} else {
|
||||
fprintf(stderr, "unknown arg: %s\n", a.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (fs.host.empty() || fs.target.empty()) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonAddFsAllow(*j, fs);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "rm-fs") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
std::optional<std::string> target;
|
||||
std::optional<size_t> index;
|
||||
for (; i < argc; ++i) {
|
||||
std::string a = mainArgv[i];
|
||||
if (a == "--target" && i + 1 < argc) {
|
||||
target = mainArgv[++i];
|
||||
} else if (a == "--index" && i + 1 < argc) {
|
||||
index = static_cast<size_t>(std::stoul(mainArgv[++i]));
|
||||
} else {
|
||||
fprintf(stderr, "unknown arg: %s\n", a.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!target && !index) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
bool ok = false;
|
||||
if (target) {
|
||||
ok = jsonRmFsByTarget(*j, *target);
|
||||
}
|
||||
if (!ok && index) {
|
||||
ok = jsonRmFsByIndex(*j, *index);
|
||||
}
|
||||
if (!ok) {
|
||||
fprintf(stderr, "no filesystem entry removed\n");
|
||||
return 1;
|
||||
}
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "rm-fs-allow") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
std::optional<std::string> target;
|
||||
std::optional<size_t> index;
|
||||
for (; i < argc; ++i) {
|
||||
std::string a = mainArgv[i];
|
||||
if (a == "--target" && i + 1 < argc) {
|
||||
target = mainArgv[++i];
|
||||
} else if (a == "--index" && i + 1 < argc) {
|
||||
index = static_cast<size_t>(std::stoul(mainArgv[++i]));
|
||||
} else {
|
||||
fprintf(stderr, "unknown arg: %s\n", a.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!target && !index) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
bool ok = false;
|
||||
if (target) {
|
||||
ok = jsonRmFsAllowByTarget(*j, *target);
|
||||
}
|
||||
if (!ok && index) {
|
||||
ok = jsonRmFsAllowByIndex(*j, *index);
|
||||
}
|
||||
if (!ok) {
|
||||
fprintf(stderr, "no filesystem_allow entry removed\n");
|
||||
return 1;
|
||||
}
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "clear-fs-allow") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonClearFsAllow(*j);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "set-command") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
if (i >= argc) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
CmdSetArg a;
|
||||
a.cmd = mainArgv[i++];
|
||||
for (; i < argc; ++i) {
|
||||
std::string t = mainArgv[i];
|
||||
if (t == "--entrypoint" && i + 1 < argc) {
|
||||
a.entrypoint = mainArgv[++i];
|
||||
} else if (t == "--cwd" && i + 1 < argc) {
|
||||
a.cwd = mainArgv[++i];
|
||||
} else if (t == "--args-prefix" && i + 1 < argc) {
|
||||
std::stringstream ss(mainArgv[++i]);
|
||||
std::string tok;
|
||||
while (ss >> tok) {
|
||||
a.argsPrefix.push_back(tok);
|
||||
}
|
||||
} else if (t == "--args-suffix" && i + 1 < argc) {
|
||||
std::stringstream ss(mainArgv[++i]);
|
||||
std::string tok;
|
||||
while (ss >> tok) {
|
||||
a.argsSuffix.push_back(tok);
|
||||
}
|
||||
} else if (t.find('=') != std::string::npos) {
|
||||
a.envKVs.push_back(t);
|
||||
} else {
|
||||
fprintf(stderr, "unknown arg: %s\n", t.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonSetCommand(*j, a);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (sub == "unset-command") {
|
||||
auto [scope, appId, baseId, i] = parseScope(3);
|
||||
if (i >= argc) {
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
std::string cmd = mainArgv[i];
|
||||
auto j = openConfig(scope, appId, baseId);
|
||||
if (!j) {
|
||||
return 1;
|
||||
}
|
||||
jsonUnsetCommand(*j, cmd);
|
||||
if (!saveConfig(scope, appId, baseId, *j)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
printConfigUsage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// ===== end: "ll-cli config ..." dispatcher =====
|
||||
|
||||
CLI::App commandParser{ _(
|
||||
"linyaps CLI\n"
|
||||
"A CLI program to run application and manage application and runtime\n") };
|
||||
auto shortConfigHelp = configShortHelp();
|
||||
auto fullConfigHelp = shortConfigHelp + configUsageLines();
|
||||
commandParser.formatter(std::make_shared<ConfigAwareFormatter>(shortConfigHelp,
|
||||
fullConfigHelp,
|
||||
configFooterMessage()));
|
||||
commandParser.option_defaults()->group(_("Options"));
|
||||
if (auto formatter = commandParser.get_formatter()) {
|
||||
formatter->label("OPTIONS", _("OPTIONS"));
|
||||
formatter->label("SUBCOMMAND", _("SUBCOMMAND"));
|
||||
formatter->label("SUBCOMMANDS", _("SUBCOMMANDS"));
|
||||
formatter->label("POSITIONALS", _("POSITIONALS"));
|
||||
formatter->label("Usage", _("Usage"));
|
||||
formatter->label("REQUIRED", _("REQUIRED"));
|
||||
}
|
||||
auto argv = commandParser.ensure_utf8(mainArgv);
|
||||
if (argc == 1) {
|
||||
std::cout << commandParser.help() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
commandParser.get_help_ptr()->description(_("Print this help message and exit"));
|
||||
commandParser.set_help_all_flag("--help-all", _("Expand all help"));
|
||||
if (auto *helpOption = commandParser.get_help_ptr()) {
|
||||
helpOption->description(_("Print this help message and exit"));
|
||||
helpOption->group(_("Options"));
|
||||
}
|
||||
if (auto *helpAllOption = commandParser.set_help_all_flag("--help-all", _("Expand all help"))) {
|
||||
helpAllOption->group(_("Options"));
|
||||
}
|
||||
commandParser.usage(_("Usage: ll-cli [OPTIONS] [SUBCOMMAND]"));
|
||||
commandParser.footer(_(R"(If you found any problems during use,
|
||||
You can report bugs to the linyaps team under this project: https://github.com/OpenAtom-Linyaps/linyaps/issues)"));
|
||||
commandParser.footer("");
|
||||
|
||||
// group empty will hide command
|
||||
constexpr auto CliHiddenGroup = "";
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "linglong/api/types/v1/helper.hpp"
|
||||
|
||||
#include "linglong/api/types/v1/BuilderProjectBuildEXT.hpp"
|
||||
#include "linglong/api/types/v1/LlCLIConfig.hpp"
|
||||
#include "linglong/api/types/v1/BuilderProjectModules.hpp"
|
||||
#include "linglong/api/types/v1/BuilderProjectPackage.hpp"
|
||||
#include "linglong/api/types/v1/ApplicationConfigurationPermissions.hpp"
|
||||
|
|
@ -50,6 +51,10 @@ std::string build;
|
|||
*/
|
||||
std::optional<BuilderProjectBuildEXT> buildext;
|
||||
/**
|
||||
* default ll-cli configuration delivered with package
|
||||
*/
|
||||
std::optional<LlCLIConfig> cliConfig;
|
||||
/**
|
||||
* command of builder project
|
||||
*/
|
||||
std::optional<std::vector<std::string>> command;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@
|
|||
#include "linglong/api/types/v1/BuilderProjectSource.hpp"
|
||||
#include "linglong/api/types/v1/BuilderProjectPackage.hpp"
|
||||
#include "linglong/api/types/v1/BuilderProjectModules.hpp"
|
||||
#include "linglong/api/types/v1/LlCLIConfig.hpp"
|
||||
#include "linglong/api/types/v1/LlCLICommandConfig.hpp"
|
||||
#include "linglong/api/types/v1/LlCLIFilesystemEntry.hpp"
|
||||
#include "linglong/api/types/v1/BuilderProjectBuildEXT.hpp"
|
||||
#include "linglong/api/types/v1/Apt.hpp"
|
||||
#include "linglong/api/types/v1/BuilderConfig.hpp"
|
||||
|
|
@ -108,6 +111,15 @@ void to_json(json & j, const Apt & x);
|
|||
void from_json(const json & j, BuilderProjectBuildEXT & x);
|
||||
void to_json(json & j, const BuilderProjectBuildEXT & x);
|
||||
|
||||
void from_json(const json & j, LlCLIFilesystemEntry & x);
|
||||
void to_json(json & j, const LlCLIFilesystemEntry & x);
|
||||
|
||||
void from_json(const json & j, LlCLICommandConfig & x);
|
||||
void to_json(json & j, const LlCLICommandConfig & x);
|
||||
|
||||
void from_json(const json & j, LlCLIConfig & x);
|
||||
void to_json(json & j, const LlCLIConfig & x);
|
||||
|
||||
void from_json(const json & j, BuilderProjectModules & x);
|
||||
void to_json(json & j, const BuilderProjectModules & x);
|
||||
|
||||
|
|
@ -383,6 +395,83 @@ j["apt"] = x.apt;
|
|||
}
|
||||
}
|
||||
|
||||
inline void from_json(const json & j, LlCLIFilesystemEntry& x) {
|
||||
x.host = j.at("host").get<std::string>();
|
||||
x.mode = get_stack_optional<std::string>(j, "mode");
|
||||
x.persist = get_stack_optional<bool>(j, "persist");
|
||||
x.target = j.at("target").get<std::string>();
|
||||
}
|
||||
|
||||
inline void to_json(json & j, const LlCLIFilesystemEntry & x) {
|
||||
j = json::object();
|
||||
j["host"] = x.host;
|
||||
if (x.mode) {
|
||||
j["mode"] = x.mode;
|
||||
}
|
||||
if (x.persist) {
|
||||
j["persist"] = x.persist;
|
||||
}
|
||||
j["target"] = x.target;
|
||||
}
|
||||
|
||||
inline void from_json(const json & j, LlCLICommandConfig& x) {
|
||||
x.argsPrefix = get_stack_optional<std::vector<std::string>>(j, "args_prefix");
|
||||
x.argsSuffix = get_stack_optional<std::vector<std::string>>(j, "args_suffix");
|
||||
x.cwd = get_stack_optional<std::string>(j, "cwd");
|
||||
x.entrypoint = get_stack_optional<std::string>(j, "entrypoint");
|
||||
x.env = get_stack_optional<std::map<std::string, std::string>>(j, "env");
|
||||
x.filesystem = get_stack_optional<std::vector<LlCLIFilesystemEntry>>(j, "filesystem");
|
||||
}
|
||||
|
||||
inline void to_json(json & j, const LlCLICommandConfig & x) {
|
||||
j = json::object();
|
||||
if (x.argsPrefix) {
|
||||
j["args_prefix"] = x.argsPrefix;
|
||||
}
|
||||
if (x.argsSuffix) {
|
||||
j["args_suffix"] = x.argsSuffix;
|
||||
}
|
||||
if (x.cwd) {
|
||||
j["cwd"] = x.cwd;
|
||||
}
|
||||
if (x.entrypoint) {
|
||||
j["entrypoint"] = x.entrypoint;
|
||||
}
|
||||
if (x.env) {
|
||||
j["env"] = x.env;
|
||||
}
|
||||
if (x.filesystem) {
|
||||
j["filesystem"] = x.filesystem;
|
||||
}
|
||||
}
|
||||
|
||||
inline void from_json(const json & j, LlCLIConfig& x) {
|
||||
x.commands = get_stack_optional<std::map<std::string, LlCLICommandConfig>>(j, "commands");
|
||||
x.env = get_stack_optional<std::map<std::string, std::string>>(j, "env");
|
||||
x.extensions = get_stack_optional<std::vector<std::string>>(j, "extensions");
|
||||
x.filesystem = get_stack_optional<std::vector<LlCLIFilesystemEntry>>(j, "filesystem");
|
||||
x.filesystemAllowOnly = get_stack_optional<std::vector<LlCLIFilesystemEntry>>(j, "filesystem_allow_only");
|
||||
}
|
||||
|
||||
inline void to_json(json & j, const LlCLIConfig & x) {
|
||||
j = json::object();
|
||||
if (x.commands) {
|
||||
j["commands"] = x.commands;
|
||||
}
|
||||
if (x.env) {
|
||||
j["env"] = x.env;
|
||||
}
|
||||
if (x.extensions) {
|
||||
j["extensions"] = x.extensions;
|
||||
}
|
||||
if (x.filesystem) {
|
||||
j["filesystem"] = x.filesystem;
|
||||
}
|
||||
if (x.filesystemAllowOnly) {
|
||||
j["filesystem_allow_only"] = x.filesystemAllowOnly;
|
||||
}
|
||||
}
|
||||
|
||||
inline void from_json(const json & j, BuilderProjectModules& x) {
|
||||
x.files = j.at("files").get<std::vector<std::string>>();
|
||||
x.name = j.at("name").get<std::string>();
|
||||
|
|
@ -468,6 +557,7 @@ inline void from_json(const json & j, BuilderProject& x) {
|
|||
x.base = j.at("base").get<std::string>();
|
||||
x.build = j.at("build").get<std::string>();
|
||||
x.buildext = get_stack_optional<BuilderProjectBuildEXT>(j, "buildext");
|
||||
x.cliConfig = get_stack_optional<LlCLIConfig>(j, "cli_config");
|
||||
x.command = get_stack_optional<std::vector<std::string>>(j, "command");
|
||||
x.exclude = get_stack_optional<std::vector<std::string>>(j, "exclude");
|
||||
x.include = get_stack_optional<std::vector<std::string>>(j, "include");
|
||||
|
|
@ -487,6 +577,9 @@ j["build"] = x.build;
|
|||
if (x.buildext) {
|
||||
j["buildext"] = x.buildext;
|
||||
}
|
||||
if (x.cliConfig) {
|
||||
j["cli_config"] = x.cliConfig;
|
||||
}
|
||||
if (x.command) {
|
||||
j["command"] = x.command;
|
||||
}
|
||||
|
|
@ -746,6 +839,7 @@ inline void from_json(const json & j, PackageInfoDisplay& x) {
|
|||
x.arch = j.at("arch").get<std::vector<std::string>>();
|
||||
x.base = j.at("base").get<std::string>();
|
||||
x.channel = j.at("channel").get<std::string>();
|
||||
x.cliConfig = get_stack_optional<LlCLIConfig>(j, "cli_config");
|
||||
x.command = get_stack_optional<std::vector<std::string>>(j, "command");
|
||||
x.compatibleVersion = get_stack_optional<std::string>(j, "compatible_version");
|
||||
x.description = get_stack_optional<std::string>(j, "description");
|
||||
|
|
@ -769,6 +863,9 @@ j = json::object();
|
|||
j["arch"] = x.arch;
|
||||
j["base"] = x.base;
|
||||
j["channel"] = x.channel;
|
||||
if (x.cliConfig) {
|
||||
j["cli_config"] = x.cliConfig;
|
||||
}
|
||||
if (x.command) {
|
||||
j["command"] = x.command;
|
||||
}
|
||||
|
|
@ -809,6 +906,7 @@ inline void from_json(const json & j, PackageInfoV2& x) {
|
|||
x.arch = j.at("arch").get<std::vector<std::string>>();
|
||||
x.base = j.at("base").get<std::string>();
|
||||
x.channel = j.at("channel").get<std::string>();
|
||||
x.cliConfig = get_stack_optional<LlCLIConfig>(j, "cli_config");
|
||||
x.command = get_stack_optional<std::vector<std::string>>(j, "command");
|
||||
x.compatibleVersion = get_stack_optional<std::string>(j, "compatible_version");
|
||||
x.description = get_stack_optional<std::string>(j, "description");
|
||||
|
|
@ -831,6 +929,9 @@ j = json::object();
|
|||
j["arch"] = x.arch;
|
||||
j["base"] = x.base;
|
||||
j["channel"] = x.channel;
|
||||
if (x.cliConfig) {
|
||||
j["cli_config"] = x.cliConfig;
|
||||
}
|
||||
if (x.command) {
|
||||
j["command"] = x.command;
|
||||
}
|
||||
|
|
@ -1248,6 +1349,9 @@ x.interactionMessageType = get_stack_optional<InteractionMessageType>(j, "Intera
|
|||
x.interactionReply = get_stack_optional<InteractionReply>(j, "InteractionReply");
|
||||
x.interactionRequest = get_stack_optional<InteractionRequest>(j, "InteractionRequest");
|
||||
x.layerInfo = get_stack_optional<LayerInfo>(j, "LayerInfo");
|
||||
x.llCLICommandConfig = get_stack_optional<LlCLICommandConfig>(j, "LlCliCommandConfig");
|
||||
x.llCLIConfig = get_stack_optional<LlCLIConfig>(j, "LlCliConfig");
|
||||
x.llCLIFilesystemEntry = get_stack_optional<LlCLIFilesystemEntry>(j, "LlCliFilesystemEntry");
|
||||
x.ociConfigurationPatch = get_stack_optional<OciConfigurationPatch>(j, "OCIConfigurationPatch");
|
||||
x.packageInfo = get_stack_optional<PackageInfo>(j, "PackageInfo");
|
||||
x.packageInfoDisplay = get_stack_optional<PackageInfoDisplay>(j, "PackageInfoDisplay");
|
||||
|
|
@ -1336,6 +1440,15 @@ j["InteractionRequest"] = x.interactionRequest;
|
|||
if (x.layerInfo) {
|
||||
j["LayerInfo"] = x.layerInfo;
|
||||
}
|
||||
if (x.llCLICommandConfig) {
|
||||
j["LlCliCommandConfig"] = x.llCLICommandConfig;
|
||||
}
|
||||
if (x.llCLIConfig) {
|
||||
j["LlCliConfig"] = x.llCLIConfig;
|
||||
}
|
||||
if (x.llCLIFilesystemEntry) {
|
||||
j["LlCliFilesystemEntry"] = x.llCLIFilesystemEntry;
|
||||
}
|
||||
if (x.ociConfigurationPatch) {
|
||||
j["OCIConfigurationPatch"] = x.ociConfigurationPatch;
|
||||
}
|
||||
|
|
@ -1435,7 +1548,7 @@ case InteractionMessageType::Install: j = "Install"; break;
|
|||
case InteractionMessageType::Uninstall: j = "Uninstall"; break;
|
||||
case InteractionMessageType::Unknown: j = "Unknown"; break;
|
||||
case InteractionMessageType::Upgrade: j = "Upgrade"; break;
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"[object Object]\": " + std::to_string(static_cast<int>(x)));
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"InteractionMessageType\": " + std::to_string(static_cast<int>(x)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1461,7 +1574,7 @@ case State::Processing: j = "Processing"; break;
|
|||
case State::Queued: j = "Queued"; break;
|
||||
case State::Succeed: j = "Succeed"; break;
|
||||
case State::Unknown: j = "Unknown"; break;
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"[object Object]\": " + std::to_string(static_cast<int>(x)));
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"State\": " + std::to_string(static_cast<int>(x)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1489,7 +1602,7 @@ case SubState::PostAction: j = "PostAction"; break;
|
|||
case SubState::PreAction: j = "PreAction"; break;
|
||||
case SubState::Uninstall: j = "Uninstall"; break;
|
||||
case SubState::Unknown: j = "Unknown"; break;
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"[object Object]\": " + std::to_string(static_cast<int>(x)));
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"SubState\": " + std::to_string(static_cast<int>(x)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1501,7 +1614,7 @@ else { throw std::runtime_error("Input JSON does not conform to schema!"); }
|
|||
inline void to_json(json & j, const Version & x) {
|
||||
switch (x) {
|
||||
case Version::The1: j = "1"; break;
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"[object Object]\": " + std::to_string(static_cast<int>(x)));
|
||||
default: throw std::runtime_error("Unexpected value in enumeration \"Version\": " + std::to_string(static_cast<int>(x)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@
|
|||
#include "linglong/api/types/v1/InteractionReply.hpp"
|
||||
#include "linglong/api/types/v1/InteractionRequest.hpp"
|
||||
#include "linglong/api/types/v1/LayerInfo.hpp"
|
||||
#include "linglong/api/types/v1/LlCLICommandConfig.hpp"
|
||||
#include "linglong/api/types/v1/LlCLIConfig.hpp"
|
||||
#include "linglong/api/types/v1/LlCLIFilesystemEntry.hpp"
|
||||
#include "linglong/api/types/v1/OciConfigurationPatch.hpp"
|
||||
#include "linglong/api/types/v1/PackageInfo.hpp"
|
||||
#include "linglong/api/types/v1/PackageInfoDisplay.hpp"
|
||||
|
|
@ -106,6 +109,9 @@ std::optional<InteractionMessageType> interactionMessageType;
|
|||
std::optional<InteractionReply> interactionReply;
|
||||
std::optional<InteractionRequest> interactionRequest;
|
||||
std::optional<LayerInfo> layerInfo;
|
||||
std::optional<LlCLICommandConfig> llCLICommandConfig;
|
||||
std::optional<LlCLIConfig> llCLIConfig;
|
||||
std::optional<LlCLIFilesystemEntry> llCLIFilesystemEntry;
|
||||
std::optional<OciConfigurationPatch> ociConfigurationPatch;
|
||||
std::optional<PackageInfo> packageInfo;
|
||||
std::optional<PackageInfoDisplay> packageInfoDisplay;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// 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
|
||||
//
|
||||
// LlCLICommandConfig.hpp data = nlohmann::json::parse(jsonString);
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "linglong/api/types/v1/helper.hpp"
|
||||
|
||||
#include "linglong/api/types/v1/LlCLIFilesystemEntry.hpp"
|
||||
|
||||
namespace linglong {
|
||||
namespace api {
|
||||
namespace types {
|
||||
namespace v1 {
|
||||
/**
|
||||
* per-command overrides for ll-cli configuration
|
||||
*/
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
/**
|
||||
* per-command overrides for ll-cli configuration
|
||||
*/
|
||||
struct LlCLICommandConfig {
|
||||
/**
|
||||
* arguments prepended before command
|
||||
*/
|
||||
std::optional<std::vector<std::string>> argsPrefix;
|
||||
/**
|
||||
* arguments appended after command
|
||||
*/
|
||||
std::optional<std::vector<std::string>> argsSuffix;
|
||||
/**
|
||||
* working directory for command
|
||||
*/
|
||||
std::optional<std::string> cwd;
|
||||
/**
|
||||
* override entrypoint for command
|
||||
*/
|
||||
std::optional<std::string> entrypoint;
|
||||
/**
|
||||
* environment variables applied to command
|
||||
*/
|
||||
std::optional<std::map<std::string, std::string>> env;
|
||||
/**
|
||||
* filesystem entries applied to command
|
||||
*/
|
||||
std::optional<std::vector<LlCLIFilesystemEntry>> filesystem;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// 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
|
||||
//
|
||||
// LlCLIConfig.hpp data = nlohmann::json::parse(jsonString);
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "linglong/api/types/v1/helper.hpp"
|
||||
|
||||
#include "linglong/api/types/v1/LlCLICommandConfig.hpp"
|
||||
#include "linglong/api/types/v1/LlCLIFilesystemEntry.hpp"
|
||||
|
||||
namespace linglong {
|
||||
namespace api {
|
||||
namespace types {
|
||||
namespace v1 {
|
||||
/**
|
||||
* default ll-cli configuration delivered with package
|
||||
*
|
||||
* default ll-cli configuration provided by package
|
||||
*/
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
/**
|
||||
* default ll-cli configuration delivered with package
|
||||
*
|
||||
* default ll-cli configuration provided by package
|
||||
*/
|
||||
struct LlCLIConfig {
|
||||
/**
|
||||
* command specific overrides
|
||||
*/
|
||||
std::optional<std::map<std::string, LlCLICommandConfig>> commands;
|
||||
/**
|
||||
* default environment variables
|
||||
*/
|
||||
std::optional<std::map<std::string, std::string>> env;
|
||||
/**
|
||||
* default extensions applied when launching app
|
||||
*/
|
||||
std::optional<std::vector<std::string>> extensions;
|
||||
/**
|
||||
* additional filesystem mounts appended to defaults
|
||||
*/
|
||||
std::optional<std::vector<LlCLIFilesystemEntry>> filesystem;
|
||||
/**
|
||||
* explicit mount allowlist; when present only these mounts are permitted
|
||||
*/
|
||||
std::optional<std::vector<LlCLIFilesystemEntry>> filesystemAllowOnly;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// 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
|
||||
//
|
||||
// LlCLIFilesystemEntry.hpp data = nlohmann::json::parse(jsonString);
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "linglong/api/types/v1/helper.hpp"
|
||||
|
||||
namespace linglong {
|
||||
namespace api {
|
||||
namespace types {
|
||||
namespace v1 {
|
||||
/**
|
||||
* filesystem entry used by ll-cli configuration
|
||||
*/
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
/**
|
||||
* filesystem entry used by ll-cli configuration
|
||||
*/
|
||||
struct LlCLIFilesystemEntry {
|
||||
/**
|
||||
* source path on host
|
||||
*/
|
||||
std::string host;
|
||||
/**
|
||||
* mount mode, either ro or rw
|
||||
*/
|
||||
std::optional<std::string> mode;
|
||||
/**
|
||||
* whether this mount should persist in sandbox storage
|
||||
*/
|
||||
std::optional<bool> persist;
|
||||
/**
|
||||
* mount target path inside container
|
||||
*/
|
||||
std::string target;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
#include "linglong/api/types/v1/helper.hpp"
|
||||
|
||||
#include "linglong/api/types/v1/LlCLIConfig.hpp"
|
||||
#include "linglong/api/types/v1/ExtensionImpl.hpp"
|
||||
#include "linglong/api/types/v1/ExtensionDefine.hpp"
|
||||
#include "linglong/api/types/v1/ApplicationConfigurationPermissions.hpp"
|
||||
|
|
@ -52,6 +53,10 @@ std::string base;
|
|||
*/
|
||||
std::string channel;
|
||||
/**
|
||||
* default ll-cli configuration provided by package
|
||||
*/
|
||||
std::optional<LlCLIConfig> cliConfig;
|
||||
/**
|
||||
* command of package info
|
||||
*/
|
||||
std::optional<std::vector<std::string>> command;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
#include "linglong/api/types/v1/helper.hpp"
|
||||
|
||||
#include "linglong/api/types/v1/LlCLIConfig.hpp"
|
||||
#include "linglong/api/types/v1/ExtensionImpl.hpp"
|
||||
#include "linglong/api/types/v1/ExtensionDefine.hpp"
|
||||
#include "linglong/api/types/v1/ApplicationConfigurationPermissions.hpp"
|
||||
|
|
@ -52,6 +53,10 @@ std::string base;
|
|||
*/
|
||||
std::string channel;
|
||||
/**
|
||||
* default ll-cli configuration provided by package
|
||||
*/
|
||||
std::optional<LlCLIConfig> cliConfig;
|
||||
/**
|
||||
* command of package info
|
||||
*/
|
||||
std::optional<std::vector<std::string>> command;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pfl_add_library(
|
|||
STATIC
|
||||
SOURCES
|
||||
# find -regex '\.\/*.+\.[ch]\(pp\)?\(.in\)?' -type f -printf '%P\n'| sort
|
||||
src/linglong/common/constants.h
|
||||
src/linglong/common/dir.cpp
|
||||
src/linglong/common/dir.h
|
||||
src/linglong/common/display.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr auto default_file_mode = 0644;
|
||||
|
|
@ -1164,7 +1164,9 @@ utils::error::Result<void> Builder::commitToLocalRepo() noexcept
|
|||
|
||||
auto info = api::types::v1::PackageInfoV2{
|
||||
.arch = { projectRef->arch.toStdString() },
|
||||
.base = project.base,
|
||||
.channel = projectRef->channel,
|
||||
.cliConfig = project.cliConfig,
|
||||
.command = project.command,
|
||||
.description = project.package.description,
|
||||
.id = project.package.id,
|
||||
|
|
@ -1176,7 +1178,6 @@ utils::error::Result<void> Builder::commitToLocalRepo() noexcept
|
|||
.version = project.package.version,
|
||||
};
|
||||
|
||||
info.base = project.base;
|
||||
if (project.runtime) {
|
||||
info.runtime = project.runtime;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -664,6 +664,9 @@ int Cli::run(const RunOptions &options)
|
|||
|
||||
runContext.enableSecurityContext(runtime::getDefaultSecurityContexts());
|
||||
|
||||
const auto &fsPolicy = runContext.filesystemPolicy();
|
||||
const bool restrictFilesystem = fsPolicy.allowListConfigured;
|
||||
|
||||
linglong::generator::ContainerCfgBuilder cfgBuilder;
|
||||
cfgBuilder.setAppId(curAppRef->id)
|
||||
.setAnnotation(generator::ANNOTATION::LAST_PID, std::to_string(pid))
|
||||
|
|
@ -673,15 +676,28 @@ int Cli::run(const RunOptions &options)
|
|||
.bindDevNode()
|
||||
.bindCgroup()
|
||||
.bindXDGRuntime()
|
||||
.bindUserGroup()
|
||||
.bindRemovableStorageMounts()
|
||||
.bindHostRoot()
|
||||
.bindHostStatics()
|
||||
.bindHome(homeEnv)
|
||||
.enablePrivateDir()
|
||||
.mapPrivate(std::string{ homeEnv } + "/.ssh", true)
|
||||
.mapPrivate(std::string{ homeEnv } + "/.gnupg", true)
|
||||
.bindIPC()
|
||||
.bindUserGroup();
|
||||
|
||||
if (!restrictFilesystem) {
|
||||
cfgBuilder.bindRemovableStorageMounts()
|
||||
.bindHostRoot()
|
||||
.bindHostStatics();
|
||||
}
|
||||
|
||||
cfgBuilder.bindHome(homeEnv).enablePrivateDir();
|
||||
|
||||
if (restrictFilesystem) {
|
||||
cfgBuilder.disableHostHomeBind();
|
||||
}
|
||||
|
||||
if (restrictFilesystem) {
|
||||
cfgBuilder.mapPrivate(std::string{ homeEnv }, true);
|
||||
} else {
|
||||
cfgBuilder.mapPrivate(std::string{ homeEnv } + "/.ssh", true)
|
||||
.mapPrivate(std::string{ homeEnv } + "/.gnupg", true);
|
||||
}
|
||||
|
||||
cfgBuilder.bindIPC()
|
||||
.forwardDefaultEnv()
|
||||
.enableSelfAdjustingMount();
|
||||
|
||||
|
|
|
|||
|
|
@ -688,13 +688,19 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
|
|||
return;
|
||||
}
|
||||
|
||||
if (!localRef) {
|
||||
auto newRef = package::Reference::fromPackageInfo(*info);
|
||||
if (!newRef) {
|
||||
taskRef.reportError(std::move(newRef).error());
|
||||
return;
|
||||
}
|
||||
auto newRef = package::Reference::fromPackageInfo(*info);
|
||||
if (!newRef) {
|
||||
taskRef.reportError(std::move(newRef).error());
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret = executePostInstallHooks(*newRef);
|
||||
if (!ret) {
|
||||
taskRef.reportError(std::move(ret).error());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!localRef) {
|
||||
auto generateCacheRet = this->tryGenerateCache(*newRef);
|
||||
if (!generateCacheRet) {
|
||||
taskRef.reportError(std::move(generateCacheRet).error());
|
||||
|
|
@ -710,31 +716,19 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
|
|||
return;
|
||||
}
|
||||
|
||||
auto newRef = package::Reference::fromPackageInfo(*info);
|
||||
if (!newRef) {
|
||||
taskRef.reportError(std::move(newRef).error());
|
||||
return;
|
||||
}
|
||||
|
||||
auto generateCacheRet = this->tryGenerateCache(*newRef);
|
||||
if (!generateCacheRet) {
|
||||
taskRef.reportError(std::move(generateCacheRet).error());
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret = removeAfterInstall(*localRef, *newRef, std::vector{ module });
|
||||
ret = removeAfterInstall(*localRef, *newRef, std::vector{ module });
|
||||
if (!ret) {
|
||||
LogE("failed to remove old reference {} after install {}: {}",
|
||||
localRef->toString(),
|
||||
packageRef.toString(),
|
||||
ret.error().message());
|
||||
}
|
||||
|
||||
ret = executePostInstallHooks(*newRef);
|
||||
if (!ret) {
|
||||
taskRef.reportError(std::move(ret).error());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
auto refSpec = fmt::format("{}:{}/{}/{}/{}",
|
||||
|
|
@ -1342,8 +1336,21 @@ void PackageManager::Update(PackageTask &taskContext,
|
|||
if (tmp.state() != linglong::api::types::v1::State::Succeed) {
|
||||
LogE("failed to rollback install {}", newRef.toString());
|
||||
}
|
||||
|
||||
auto ret = executePostUninstallHooks(newRef);
|
||||
if (!ret) {
|
||||
LogE("failed to rollback execute uninstall hooks: {}", ret.error());
|
||||
}
|
||||
});
|
||||
|
||||
auto result = executePostInstallHooks(newRef);
|
||||
if (!result) {
|
||||
taskContext.updateState(linglong::api::types::v1::State::Failed,
|
||||
"Failed to execute postInstall hooks.\n"
|
||||
+ result.error().message());
|
||||
return;
|
||||
}
|
||||
|
||||
auto oldRefLayerItem = this->repo.getLayerItem(oldRef);
|
||||
|
||||
auto msg = fmt::format("Upgrade {} (from repo : {}) to {} (from repo : {}) success",
|
||||
|
|
@ -1377,12 +1384,6 @@ void PackageManager::Update(PackageTask &taskContext,
|
|||
return;
|
||||
}
|
||||
|
||||
ret = executePostInstallHooks(newRef);
|
||||
if (!ret) {
|
||||
qCritical() << "failed to execute post install hooks" << ret.error().message();
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = this->tryGenerateCache(newRef);
|
||||
if (!result) {
|
||||
taskContext.reportError(
|
||||
|
|
@ -1504,14 +1505,14 @@ void PackageManager::pullDependency(PackageTask &taskContext,
|
|||
transaction.addRollBack([this, remoteRef = *remote, module]() noexcept {
|
||||
auto result = this->repo.remove(remoteRef.reference, module);
|
||||
if (!result) {
|
||||
qCritical() << result.error();
|
||||
Q_ASSERT(false);
|
||||
LogE("failed to remove remote reference: {} : {}",
|
||||
remoteRef.reference.toString(),
|
||||
result.error().message());
|
||||
}
|
||||
|
||||
result = executePostUninstallHooks(remoteRef.reference);
|
||||
if (!result) {
|
||||
qCritical() << result.error();
|
||||
Q_ASSERT(false);
|
||||
LogE("failed to rollback execute uninstall hooks: {}", result.error());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ public
|
|||
const std::vector<std::string> &modules) noexcept;
|
||||
utils::error::Result<void> tryGenerateCache(const package::Reference &ref) noexcept;
|
||||
utils::error::Result<void> executePostInstallHooks(const package::Reference &ref) noexcept;
|
||||
utils::error::Result<void> executePostUninstallHooks(const package::Reference &ref) noexcept;
|
||||
void pullDependency(PackageTask &taskContext,
|
||||
const api::types::v1::PackageInfoV2 &info,
|
||||
const std::string &module) noexcept;
|
||||
|
|
@ -169,7 +170,6 @@ private:
|
|||
Prune(std::vector<api::types::v1::PackageInfoV2> &removedInfo) noexcept;
|
||||
utils::error::Result<void> generateCache(const package::Reference &ref) noexcept;
|
||||
utils::error::Result<void> removeCache(const package::Reference &ref) noexcept;
|
||||
utils::error::Result<void> executePostUninstallHooks(const package::Reference &ref) noexcept;
|
||||
|
||||
linglong::repo::OSTreeRepo &repo; // NOLINT
|
||||
PackageTaskQueue tasks;
|
||||
|
|
|
|||
|
|
@ -206,6 +206,11 @@ utils::error::Result<void> RefInstallationAction::install(PackageTask &task)
|
|||
if (tmp.state() != linglong::api::types::v1::State::Succeed) {
|
||||
LogE("failed to rollback install {}", newRef.toString());
|
||||
}
|
||||
|
||||
auto result = pm.executePostUninstallHooks(newRef);
|
||||
if (!result) {
|
||||
LogE("failed to rollback execute uninstall hooks: {}", result.error());
|
||||
}
|
||||
});
|
||||
|
||||
return LINGLONG_OK;
|
||||
|
|
@ -224,6 +229,14 @@ utils::error::Result<void> RefInstallationAction::postInstall(PackageTask &task)
|
|||
|
||||
const auto &newRef = operation.newRef->reference;
|
||||
const auto &oldRef = operation.oldRef;
|
||||
|
||||
auto ret = pm.executePostInstallHooks(newRef);
|
||||
if (!ret) {
|
||||
task.updateState(linglong::api::types::v1::State::Failed,
|
||||
"Failed to execute postInstall hooks.\n" + ret.error().message());
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
|
||||
auto layer = this->repo.getLayerItem(newRef);
|
||||
if (!layer) {
|
||||
task.reportError(
|
||||
|
|
@ -256,13 +269,6 @@ utils::error::Result<void> RefInstallationAction::postInstall(PackageTask &task)
|
|||
}
|
||||
}
|
||||
|
||||
auto ret = pm.executePostInstallHooks(newRef);
|
||||
if (!ret) {
|
||||
task.updateState(linglong::api::types::v1::State::Failed,
|
||||
"Failed to execute postInstall hooks.\n" + ret.error().message());
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
task.updateState(linglong::api::types::v1::State::Succeed,
|
||||
fmt::format("Install {} (from repo: {}) success",
|
||||
|
|
|
|||
|
|
@ -329,6 +329,12 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
|
|||
const auto &newRef = operation.newRef->reference;
|
||||
const auto &oldRef = operation.oldRef;
|
||||
|
||||
auto ret = pm.executePostInstallHooks(newRef);
|
||||
if (!ret) {
|
||||
task.reportError(std::move(ret).error());
|
||||
return LINGLONG_ERR("failed to execute post install hooks");
|
||||
}
|
||||
|
||||
if (!oldRef) {
|
||||
auto mergeRet = repo.mergeModules();
|
||||
if (!mergeRet) {
|
||||
|
|
@ -360,12 +366,6 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
|
|||
}
|
||||
}
|
||||
|
||||
auto ret = pm.executePostInstallHooks(newRef);
|
||||
if (!ret) {
|
||||
task.reportError(std::move(ret).error());
|
||||
return LINGLONG_ERR("failed to execute post install hooks");
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
task.updateState(linglong::api::types::v1::State::Succeed, "install uab successfully");
|
||||
|
||||
|
|
@ -426,6 +426,11 @@ utils::error::Result<void> UabInstallationAction::installUabLayer(
|
|||
if (!ret) {
|
||||
LogE("rollback importLayerDir failed: {}", ret.error());
|
||||
}
|
||||
|
||||
ret = pm.executePostUninstallHooks(ref);
|
||||
if (!ret) {
|
||||
LogE("failed to rollback execute uninstall hooks: {}", ret.error());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -508,4 +513,4 @@ utils::error::Result<void> UabInstallationAction::installDistributionModeUAB()
|
|||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
} // namespace linglong::service
|
||||
} // namespace linglong::service
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
*/
|
||||
|
||||
#include "run_context.h"
|
||||
#include "configure.h"
|
||||
|
||||
#include "linglong/api/types/v1/Generators.hpp"
|
||||
|
||||
#include "linglong/common/display.h"
|
||||
#include "linglong/extension/extension.h"
|
||||
|
|
@ -11,8 +14,359 @@
|
|||
|
||||
#include <utility>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace linglong::runtime {
|
||||
|
||||
static std::vector<std::filesystem::path> configBasesUserFirst()
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
std::vector<fs::path> bases;
|
||||
std::unordered_set<std::string> seen;
|
||||
|
||||
auto addBase = [&](const fs::path &candidate) {
|
||||
if (candidate.empty()) {
|
||||
return;
|
||||
}
|
||||
auto normalized = candidate.lexically_normal();
|
||||
auto key = normalized.string();
|
||||
if (key.empty()) {
|
||||
return;
|
||||
}
|
||||
if (seen.insert(key).second) {
|
||||
bases.emplace_back(std::move(normalized));
|
||||
}
|
||||
};
|
||||
|
||||
if (const char *xdgConfigHome = ::getenv("XDG_CONFIG_HOME");
|
||||
xdgConfigHome && xdgConfigHome[0] != '\0') {
|
||||
addBase(fs::path(xdgConfigHome) / "linglong");
|
||||
} else if (const char *homeEnv = ::getenv("HOME"); homeEnv && homeEnv[0] != '\0') {
|
||||
addBase(fs::path(homeEnv) / ".config" / "linglong");
|
||||
}
|
||||
|
||||
addBase(fs::path(LINGLONG_DATA_DIR) / "config");
|
||||
return bases;
|
||||
}
|
||||
|
||||
static std::vector<std::filesystem::path> configBasesFallbackFirst()
|
||||
{
|
||||
auto bases = configBasesUserFirst();
|
||||
if (bases.size() <= 1) {
|
||||
return bases;
|
||||
}
|
||||
std::rotate(bases.begin(), std::prev(bases.end()), bases.end());
|
||||
return bases;
|
||||
}
|
||||
|
||||
static std::vector<std::string> loadExtensionsFromConfig(const std::string &appId)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
std::vector<std::string> result;
|
||||
|
||||
// 2. 定义解析函数:从指定 JSON 文件提取 "extensions" 数组,并加入 result
|
||||
auto parseExtensions = [&](const fs::path &p) {
|
||||
try {
|
||||
if (!fs::exists(p)) return;
|
||||
std::ifstream in(p);
|
||||
if (!in.is_open()) return;
|
||||
nlohmann::json j;
|
||||
in >> j;
|
||||
if (!j.contains("extensions") || !j.at("extensions").is_array()) return;
|
||||
for (const auto &elem : j.at("extensions")) {
|
||||
if (elem.is_string()) {
|
||||
std::string ext = elem.get<std::string>();
|
||||
if (std::find(result.begin(), result.end(), ext) == result.end()) {
|
||||
result.emplace_back(std::move(ext));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// 文件不存在或解析失败时忽略
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto &base : configBasesUserFirst()) {
|
||||
parseExtensions(base / "config.json");
|
||||
if (!appId.empty()) {
|
||||
parseExtensions(base / "apps" / appId / "config.json");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<std::string> loadExtensionsFromBase(const std::string &baseId)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
std::vector<std::string> result;
|
||||
|
||||
if (baseId.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto &root : configBasesUserFirst()) {
|
||||
fs::path cfgPath = root / "base" / baseId / "config.json";
|
||||
try {
|
||||
if (!fs::exists(cfgPath)) {
|
||||
continue;
|
||||
}
|
||||
std::ifstream in(cfgPath);
|
||||
if (!in.is_open()) {
|
||||
continue;
|
||||
}
|
||||
nlohmann::json j;
|
||||
in >> j;
|
||||
if (!j.contains("extensions") || !j.at("extensions").is_array()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &elem : j.at("extensions")) {
|
||||
if (elem.is_string()) {
|
||||
std::string ext = elem.get<std::string>();
|
||||
if (std::find(result.begin(), result.end(), ext) == result.end()) {
|
||||
result.emplace_back(std::move(ext));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// ignore parse errors
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===== begin: config helpers for env/mount/commands (Global->Base->App merge) =====
|
||||
using json = nlohmann::json;
|
||||
|
||||
static json loadMergedJsonWithBase(const std::string &appId, const std::string &baseId)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
json merged = json::object();
|
||||
|
||||
auto readIfExists = [](const fs::path &p) -> std::optional<json> {
|
||||
try {
|
||||
if (!fs::exists(p)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::ifstream in(p);
|
||||
if (!in.is_open()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
json j;
|
||||
in >> j;
|
||||
return j;
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto &root : configBasesFallbackFirst()) {
|
||||
if (auto g = readIfExists(root / "config.json")) {
|
||||
merged.merge_patch(*g);
|
||||
}
|
||||
if (!baseId.empty()) {
|
||||
if (auto b = readIfExists(root / "base" / baseId / "config.json")) {
|
||||
merged.merge_patch(*b);
|
||||
}
|
||||
}
|
||||
if (!appId.empty()) {
|
||||
if (auto a = readIfExists(root / "apps" / appId / "config.json")) {
|
||||
merged.merge_patch(*a);
|
||||
}
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
static std::string expandUserHome(const std::string &path)
|
||||
{
|
||||
if (path == "~" || path.rfind("~/", 0) == 0) {
|
||||
const char *home = ::getenv("HOME");
|
||||
if (home && home[0]) {
|
||||
return path == "~" ? std::string(home) : (std::string(home) + path.substr(1));
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static void collectEnvFromJson(const json &j, std::vector<std::string> &out)
|
||||
{
|
||||
if (!j.contains("env") || !j.at("env").is_object()) {
|
||||
return;
|
||||
}
|
||||
for (auto it = j.at("env").begin(); it != j.at("env").end(); ++it) {
|
||||
const std::string key = it.key();
|
||||
std::string val = it.value().is_string() ? it.value().get<std::string>() : std::string();
|
||||
if (val.find('$') != std::string::npos) {
|
||||
qWarning() << "ignore env with variable expansion:" << QString::fromStdString(key);
|
||||
continue;
|
||||
}
|
||||
if (!key.empty() && key.back() == '+') {
|
||||
out.emplace_back(key.substr(0, key.size() - 1) + "+=" + val);
|
||||
} else {
|
||||
out.emplace_back(key + "=" + val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<ocppi::runtime::config::types::Mount>
|
||||
parseFilesystemMounts(const std::string &appId, const json &arr)
|
||||
{
|
||||
using Mount = ocppi::runtime::config::types::Mount;
|
||||
std::vector<Mount> mounts;
|
||||
if (!arr.is_array()) {
|
||||
return mounts;
|
||||
}
|
||||
for (const auto &e : arr) {
|
||||
if (!e.is_object()) {
|
||||
continue;
|
||||
}
|
||||
std::string host = e.value("host", "");
|
||||
std::string target = e.value("target", "");
|
||||
std::string mode = e.value("mode", "ro");
|
||||
bool persist = e.value("persist", false);
|
||||
|
||||
if (host.empty() || target.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (host.find('$') != std::string::npos || target.find('$') != std::string::npos) {
|
||||
qWarning() << "ignore mount with variable expansion:" << QString::fromStdString(host)
|
||||
<< "->" << QString::fromStdString(target);
|
||||
continue;
|
||||
}
|
||||
|
||||
host = expandUserHome(host);
|
||||
if (persist) {
|
||||
const char *home = ::getenv("HOME");
|
||||
if (home && home[0] && !appId.empty()) {
|
||||
std::filesystem::path p(home);
|
||||
p /= ".var/app";
|
||||
p /= appId;
|
||||
p /= std::filesystem::path(host).filename();
|
||||
host = p.string();
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::path hostPath(host);
|
||||
if (!std::filesystem::exists(hostPath, ec)) {
|
||||
ec.clear();
|
||||
if (!std::filesystem::create_directories(hostPath, ec) && ec) {
|
||||
ec.clear();
|
||||
auto parent = hostPath.parent_path();
|
||||
if (!parent.empty()) {
|
||||
std::filesystem::create_directories(parent, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ec || !std::filesystem::exists(hostPath, ec)) {
|
||||
qWarning() << "failed to prepare persist directory for"
|
||||
<< QString::fromStdString(host) << ":" << ec.message().c_str();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Mount m;
|
||||
m.type = "bind";
|
||||
m.source = host;
|
||||
m.destination = target;
|
||||
m.options = { { (mode == "rw" ? "rw" : "ro"), "rbind" } };
|
||||
mounts.emplace_back(std::move(m));
|
||||
}
|
||||
|
||||
return mounts;
|
||||
}
|
||||
|
||||
static void collectMountsFromJson(const std::string &appId,
|
||||
const json &j,
|
||||
std::vector<ocppi::runtime::config::types::Mount> &out)
|
||||
{
|
||||
if (!j.contains("filesystem") || !j.at("filesystem").is_array()) {
|
||||
return;
|
||||
}
|
||||
auto mounts = parseFilesystemMounts(appId, j.at("filesystem"));
|
||||
std::move(mounts.begin(), mounts.end(), std::back_inserter(out));
|
||||
}
|
||||
|
||||
struct CommandSettings {
|
||||
std::vector<std::string> envKVs;
|
||||
std::vector<ocppi::runtime::config::types::Mount> mounts;
|
||||
std::vector<std::string> argsPrefix;
|
||||
std::vector<std::string> argsSuffix;
|
||||
std::optional<std::string> entrypoint;
|
||||
std::optional<std::string> cwd;
|
||||
};
|
||||
|
||||
static const json *pickCommandNode(const json &merged, const std::string &execName)
|
||||
{
|
||||
if (!merged.contains("commands") || !merged.at("commands").is_object()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &cmds = merged.at("commands");
|
||||
if (auto it = cmds.find(execName); it != cmds.end() && it->is_object()) {
|
||||
return &(*it);
|
||||
}
|
||||
if (auto it = cmds.find("*"); it != cmds.end() && it->is_object()) {
|
||||
return &(*it);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void loadStrVec(const json &node, const char *key, std::vector<std::string> &out)
|
||||
{
|
||||
if (!node.contains(key) || !node.at(key).is_array()) {
|
||||
return;
|
||||
}
|
||||
for (const auto &v : node.at(key)) {
|
||||
if (v.is_string()) {
|
||||
out.emplace_back(v.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CommandSettings parseCommandSettings(const std::string &appId, const json &node)
|
||||
{
|
||||
CommandSettings cs;
|
||||
if (node.contains("env") && node.at("env").is_object()) {
|
||||
for (auto it = node.at("env").begin(); it != node.at("env").end(); ++it) {
|
||||
const std::string key = it.key();
|
||||
std::string val = it.value().is_string() ? it.value().get<std::string>() : std::string();
|
||||
if (val.find('$') != std::string::npos) {
|
||||
qWarning() << "ignore env with variable expansion in command settings:"
|
||||
<< QString::fromStdString(key);
|
||||
continue;
|
||||
}
|
||||
if (!key.empty() && key.back() == '+') {
|
||||
cs.envKVs.emplace_back(key.substr(0, key.size() - 1) + "+=" + val);
|
||||
} else {
|
||||
cs.envKVs.emplace_back(key + "=" + val);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.contains("filesystem") && node.at("filesystem").is_array()) {
|
||||
collectMountsFromJson(appId, node, cs.mounts);
|
||||
}
|
||||
loadStrVec(node, "args_prefix", cs.argsPrefix);
|
||||
loadStrVec(node, "args_suffix", cs.argsSuffix);
|
||||
if (node.contains("entrypoint") && node.at("entrypoint").is_string()) {
|
||||
cs.entrypoint = node.at("entrypoint").get<std::string>();
|
||||
}
|
||||
if (node.contains("cwd") && node.at("cwd").is_string()) {
|
||||
cs.cwd = node.at("cwd").get<std::string>();
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
// ===== end: config helpers =====
|
||||
|
||||
|
||||
RuntimeLayer::RuntimeLayer(package::Reference ref, RunContext &context)
|
||||
: reference(std::move(ref))
|
||||
, runContext(context)
|
||||
|
|
@ -82,6 +436,8 @@ utils::error::Result<void> RunContext::resolve(const linglong::package::Referenc
|
|||
{
|
||||
LINGLONG_TRACE("resolve RunContext from runnable " + runnable.toString());
|
||||
|
||||
filesystemPolicyCache.reset();
|
||||
|
||||
containerID = runtime::genContainerID(runnable);
|
||||
|
||||
auto item = repo.getLayerItem(runnable);
|
||||
|
|
@ -155,8 +511,50 @@ utils::error::Result<void> RunContext::resolve(const linglong::package::Referenc
|
|||
}
|
||||
|
||||
// 手动解析多个扩展
|
||||
// 先从命令行选项或配置文件获取扩展列表
|
||||
// 先从命令行选项或应用/全局配置获取扩展列表
|
||||
std::vector<std::string> extRefs;
|
||||
if (options.extensionRefs && !options.extensionRefs->empty()) {
|
||||
auto manualExtensionDef = makeManualExtensionDefine(*options.extensionRefs);
|
||||
extRefs = *options.extensionRefs;
|
||||
} else {
|
||||
extRefs = loadExtensionsFromConfig(runnable.id);
|
||||
}
|
||||
|
||||
if (extRefs.empty() && info.cliConfig && info.cliConfig->extensions) {
|
||||
extRefs = *info.cliConfig->extensions;
|
||||
}
|
||||
|
||||
// 如果未获取到扩展列表,则尝试根据 base 层加载
|
||||
if (extRefs.empty()) {
|
||||
// 获取 baseId
|
||||
std::string baseId;
|
||||
|
||||
// 1. 优先使用 ResolveOptions::baseRef(如果提供)
|
||||
if (options.baseRef && !options.baseRef->empty()) {
|
||||
// 假设存在 FuzzyReference::parse,可解析出 id 部分
|
||||
auto baseRef = linglong::package::FuzzyReference::parse(*options.baseRef);
|
||||
if (baseRef) {
|
||||
baseId = baseRef->id;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 否则从当前运行包信息中获取
|
||||
if (baseId.empty()) {
|
||||
auto item = repo.getLayerItem(runnable);
|
||||
if (item && !item->info.base.empty()) {
|
||||
baseId = item->info.base;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 若 baseId 非空,则读取 base 配置
|
||||
if (!baseId.empty()) {
|
||||
extRefs = loadExtensionsFromBase(baseId);
|
||||
}
|
||||
}
|
||||
|
||||
// 若 extRefs 非空,继续使用原有的手动解析逻辑
|
||||
if (!extRefs.empty()) {
|
||||
auto manualExtensionDef = makeManualExtensionDefine(extRefs);
|
||||
if (!manualExtensionDef) {
|
||||
return LINGLONG_ERR(manualExtensionDef);
|
||||
}
|
||||
|
|
@ -177,6 +575,8 @@ utils::error::Result<void> RunContext::resolve(const api::types::v1::BuilderProj
|
|||
{
|
||||
LINGLONG_TRACE("resolve RunContext from builder project " + target.package.id);
|
||||
|
||||
filesystemPolicyCache.reset();
|
||||
|
||||
auto targetRef = package::Reference::fromBuilderProject(target);
|
||||
if (!targetRef) {
|
||||
return LINGLONG_ERR(targetRef);
|
||||
|
|
@ -572,10 +972,176 @@ RunContext::fillContextCfg(linglong::generator::ContainerCfgBuilder &builder)
|
|||
return res;
|
||||
}
|
||||
|
||||
// === begin: merge Global->Base->App config ===
|
||||
std::string currentAppId;
|
||||
if (appLayer) currentAppId = appLayer->getReference().id;
|
||||
else if (!targetId.empty()) currentAppId = targetId;
|
||||
|
||||
std::string currentBaseId;
|
||||
if (baseLayer) currentBaseId = baseLayer->getReference().id;
|
||||
|
||||
json mergedCfg = json::object();
|
||||
|
||||
auto mergeCliConfig = [&](RuntimeLayer *layer) {
|
||||
if (layer == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto item = layer->getCachedItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (item->info.cliConfig) {
|
||||
json cfg = *(item->info.cliConfig);
|
||||
if (mergedCfg.empty()) {
|
||||
mergedCfg = std::move(cfg);
|
||||
} else {
|
||||
mergedCfg.merge_patch(cfg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mergeCliConfig(baseLayer ? &*baseLayer : nullptr);
|
||||
mergeCliConfig(appLayer ? &*appLayer : nullptr);
|
||||
|
||||
auto configFromDirs = loadMergedJsonWithBase(currentAppId, currentBaseId);
|
||||
if (mergedCfg.empty()) {
|
||||
mergedCfg = configFromDirs;
|
||||
} else {
|
||||
mergedCfg.merge_patch(configFromDirs);
|
||||
}
|
||||
std::optional<std::string> mergedPath;
|
||||
|
||||
// 1) common env
|
||||
{
|
||||
std::vector<std::string> envKVs;
|
||||
collectEnvFromJson(mergedCfg, envKVs);
|
||||
if (!envKVs.empty()) {
|
||||
std::map<std::string, std::string> genEnv;
|
||||
std::string basePath;
|
||||
if (auto sysPath = ::getenv("PATH")) {
|
||||
basePath = sysPath;
|
||||
}
|
||||
auto extPathIt = environment.find("PATH");
|
||||
for (const auto &kv : envKVs) {
|
||||
auto pos = kv.find("+=");
|
||||
if (pos != std::string::npos) {
|
||||
auto key = kv.substr(0, pos);
|
||||
auto add = kv.substr(pos + 2);
|
||||
if (key == "PATH") {
|
||||
if (genEnv.count("PATH")) {
|
||||
genEnv["PATH"] += ":" + add;
|
||||
} else if (extPathIt != environment.end()) {
|
||||
genEnv["PATH"] =
|
||||
extPathIt->second.empty() ? add : extPathIt->second + ":" + add;
|
||||
} else if (!basePath.empty()) {
|
||||
genEnv["PATH"] = basePath + ":" + add;
|
||||
} else {
|
||||
genEnv["PATH"] = add;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "ignore '+=' env for key:" << QString::fromStdString(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto eq = kv.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
genEnv[kv.substr(0, eq)] = kv.substr(eq + 1);
|
||||
}
|
||||
if (!genEnv.empty()) {
|
||||
if (auto it = genEnv.find("PATH"); it != genEnv.end()) {
|
||||
mergedPath = it->second;
|
||||
}
|
||||
builder.appendEnv(genEnv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) common filesystem
|
||||
{
|
||||
const auto &fsPolicy = filesystemPolicy();
|
||||
if (fsPolicy.allowListConfigured) {
|
||||
if (!fsPolicy.allowList.empty()) {
|
||||
auto allowList = fsPolicy.allowList;
|
||||
builder.addExtraMounts(std::move(allowList));
|
||||
}
|
||||
} else if (!fsPolicy.extra.empty()) {
|
||||
auto extraMounts = fsPolicy.extra;
|
||||
builder.addExtraMounts(std::move(extraMounts));
|
||||
}
|
||||
}
|
||||
// === end: merge Global->Base->App config ===
|
||||
|
||||
if (!environment.empty()) {
|
||||
if (auto it = environment.find("PATH"); it != environment.end()) {
|
||||
mergedPath = it->second;
|
||||
}
|
||||
builder.appendEnv(environment);
|
||||
}
|
||||
|
||||
// === begin: command-level settings (highest priority) ===
|
||||
{
|
||||
std::string execName = currentAppId;
|
||||
if (!execName.empty()) {
|
||||
if (const json *node = pickCommandNode(mergedCfg, execName)) {
|
||||
CommandSettings cs = parseCommandSettings(currentAppId, *node);
|
||||
|
||||
if (!cs.envKVs.empty()) {
|
||||
std::map<std::string, std::string> cmdEnv;
|
||||
std::string basePath;
|
||||
if (auto sysPath = ::getenv("PATH")) {
|
||||
basePath = sysPath;
|
||||
}
|
||||
auto extPathIt = environment.find("PATH");
|
||||
for (const auto &kv : cs.envKVs) {
|
||||
auto posp = kv.find("+=");
|
||||
if (posp != std::string::npos) {
|
||||
auto key = kv.substr(0, posp);
|
||||
auto add = kv.substr(posp + 2);
|
||||
if (key == "PATH") {
|
||||
if (cmdEnv.count("PATH")) {
|
||||
cmdEnv["PATH"] += ":" + add;
|
||||
} else if (mergedPath) {
|
||||
cmdEnv["PATH"] =
|
||||
mergedPath->empty() ? add : *mergedPath + ":" + add;
|
||||
} else if (extPathIt != environment.end()) {
|
||||
cmdEnv["PATH"] =
|
||||
extPathIt->second.empty()
|
||||
? add
|
||||
: extPathIt->second + ":" + add;
|
||||
} else if (!basePath.empty()) {
|
||||
cmdEnv["PATH"] = basePath + ":" + add;
|
||||
} else {
|
||||
cmdEnv["PATH"] = add;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "ignore '+=' env for key in command settings:"
|
||||
<< QString::fromStdString(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto eq = kv.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
cmdEnv[kv.substr(0, eq)] = kv.substr(eq + 1);
|
||||
}
|
||||
if (!cmdEnv.empty()) {
|
||||
if (auto it = cmdEnv.find("PATH"); it != cmdEnv.end()) {
|
||||
mergedPath = it->second;
|
||||
}
|
||||
builder.appendEnv(cmdEnv);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cs.mounts.empty()) builder.addExtraMounts(cs.mounts);
|
||||
// TODO: when builder exposes API for entrypoint/cwd/args, apply here as well.
|
||||
}
|
||||
}
|
||||
}
|
||||
// === end: command-level settings ===
|
||||
|
||||
detectDisplaySystem(builder);
|
||||
|
||||
for (auto ctx = securityContexts.begin(); ctx != securityContexts.end(); ++ctx) {
|
||||
|
|
@ -726,6 +1292,45 @@ utils::error::Result<std::filesystem::path> RunContext::getBaseLayerPath() const
|
|||
return std::filesystem::path{ layerDir->absolutePath().toStdString() };
|
||||
}
|
||||
|
||||
std::string RunContext::currentAppId() const
|
||||
{
|
||||
if (appLayer) {
|
||||
return appLayer->getReference().id;
|
||||
}
|
||||
return targetId;
|
||||
}
|
||||
|
||||
const RunContext::FilesystemPolicy &RunContext::filesystemPolicy() const
|
||||
{
|
||||
if (!filesystemPolicyCache) {
|
||||
FilesystemPolicy policy;
|
||||
|
||||
auto appId = currentAppId();
|
||||
std::string baseId;
|
||||
if (baseLayer) {
|
||||
baseId = baseLayer->getReference().id;
|
||||
}
|
||||
|
||||
auto mergedCfg = loadMergedJsonWithBase(appId, baseId);
|
||||
if (auto it = mergedCfg.find("filesystem_allow_only"); it != mergedCfg.end()) {
|
||||
policy.allowListConfigured = true;
|
||||
if (it->is_array()) {
|
||||
policy.allowList = parseFilesystemMounts(appId, *it);
|
||||
}
|
||||
}
|
||||
|
||||
if (!policy.allowListConfigured) {
|
||||
if (auto it = mergedCfg.find("filesystem"); it != mergedCfg.end()) {
|
||||
policy.extra = parseFilesystemMounts(appId, *it);
|
||||
}
|
||||
}
|
||||
|
||||
filesystemPolicyCache = std::move(policy);
|
||||
}
|
||||
|
||||
return *filesystemPolicyCache;
|
||||
}
|
||||
|
||||
utils::error::Result<std::filesystem::path> RunContext::getRuntimeLayerPath() const
|
||||
{
|
||||
LINGLONG_TRACE("get runtime layer path");
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
#include <filesystem>
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace linglong::runtime {
|
||||
|
||||
|
|
@ -65,6 +68,13 @@ struct ResolveOptions
|
|||
class RunContext
|
||||
{
|
||||
public:
|
||||
struct FilesystemPolicy
|
||||
{
|
||||
bool allowListConfigured{ false };
|
||||
std::vector<ocppi::runtime::config::types::Mount> allowList;
|
||||
std::vector<ocppi::runtime::config::types::Mount> extra;
|
||||
};
|
||||
|
||||
RunContext(repo::OSTreeRepo &r)
|
||||
: repo(r)
|
||||
{
|
||||
|
|
@ -83,6 +93,9 @@ public:
|
|||
|
||||
repo::OSTreeRepo &getRepo() const { return repo; }
|
||||
|
||||
std::string currentAppId() const;
|
||||
const FilesystemPolicy &filesystemPolicy() const;
|
||||
|
||||
const std::string &getContainerId() const { return containerID; }
|
||||
|
||||
const std::optional<RuntimeLayer> &getBaseLayer() const { return baseLayer; }
|
||||
|
|
@ -128,6 +141,7 @@ private:
|
|||
std::string containerID;
|
||||
std::filesystem::path bundle;
|
||||
std::map<std::string, std::string> environment;
|
||||
mutable std::optional<FilesystemPolicy> filesystemPolicyCache;
|
||||
};
|
||||
|
||||
} // namespace linglong::runtime
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pfl_add_executable(
|
|||
DISABLE_INSTALL
|
||||
SOURCES
|
||||
# find -regex '\./src/.+\.[ch]\(pp\)?' -type f -printf '%P\n'| sort
|
||||
src/common/tempdir.h
|
||||
src/linglong/builder/linglong_builder_test.cpp
|
||||
src/linglong/builder/source_fetcher_test.cpp
|
||||
src/linglong/common/strings_test.cpp
|
||||
|
|
@ -24,6 +25,8 @@ pfl_add_executable(
|
|||
src/linglong/package/architecture_test.cpp
|
||||
src/linglong/package/fallback_version_test.cpp
|
||||
src/linglong/package/layer_packager_test.cpp
|
||||
src/linglong/package_manager/action_test.cpp
|
||||
src/linglong/package_manager/uab_installation_test.cpp
|
||||
src/linglong/package/reference_test.cpp
|
||||
src/linglong/package/semver_compare_test.cpp
|
||||
src/linglong/package/semver_increment_test.cpp
|
||||
|
|
@ -33,8 +36,6 @@ pfl_add_executable(
|
|||
src/linglong/package/uab_file_test.cpp
|
||||
src/linglong/package/version_test.cpp
|
||||
src/linglong/package/versionv2_test.cpp
|
||||
src/linglong/package_manager/action_test.cpp
|
||||
src/linglong/package_manager/uab_installation_test.cpp
|
||||
src/linglong/repo/client_factory_test.cpp
|
||||
src/linglong/repo/config_test.cpp
|
||||
src/linglong/repo/ostree_repo_test.cpp
|
||||
|
|
@ -42,6 +43,7 @@ pfl_add_executable(
|
|||
src/linglong/utils/command_test.cpp
|
||||
src/linglong/utils/error/error_test.cpp
|
||||
src/linglong/utils/file.cpp
|
||||
src/linglong/utils/filelock_test.cpp
|
||||
src/linglong/utils/gkeyfile_wrapper_test.cpp
|
||||
src/linglong/utils/log.cpp
|
||||
src/linglong/utils/namespce.cpp
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "linglong/common/strings.h"
|
||||
#include "linglong/utils/command/env.h"
|
||||
#include "linglong/utils/error/error.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
|
@ -14,9 +15,11 @@
|
|||
using namespace linglong::utils::error;
|
||||
|
||||
using namespace linglong::common;
|
||||
using linglong::utils::command::EnvironmentVariableGuard;
|
||||
|
||||
TEST(Error, New)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR");
|
||||
return LINGLONG_ERR("message", -1);
|
||||
|
|
@ -31,6 +34,7 @@ TEST(Error, New)
|
|||
|
||||
TEST(Error, WrapResult)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR");
|
||||
auto res = []() -> Result<void> {
|
||||
|
|
@ -59,6 +63,7 @@ TEST(Error, WrapResult)
|
|||
|
||||
TEST(Error, WrapError)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR");
|
||||
auto res = []() -> Result<void> {
|
||||
|
|
@ -82,6 +87,7 @@ TEST(Error, WrapError)
|
|||
|
||||
TEST(Error, WarpErrorCode)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR");
|
||||
return LINGLONG_ERR("app install failed", ErrorCode::AppInstallFailed);
|
||||
|
|
@ -95,6 +101,7 @@ TEST(Error, WarpErrorCode)
|
|||
|
||||
TEST(Error, WarpStdErrorCode)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR");
|
||||
std::error_code ec;
|
||||
|
|
@ -112,6 +119,7 @@ TEST(Error, WarpStdErrorCode)
|
|||
|
||||
TEST(Error, WarpQtFile)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR");
|
||||
QFile file("/not_exists_file");
|
||||
|
|
@ -127,10 +135,9 @@ TEST(Error, WarpQtFile)
|
|||
ASSERT_TRUE(strings::contains(msg, "No such file or directory")) << msg;
|
||||
}
|
||||
|
||||
// 在现有的 error_test.cpp 文件末尾添加以下测试
|
||||
|
||||
TEST(Error, WarpGError)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with GError");
|
||||
g_autoptr(GError) gErr =
|
||||
|
|
@ -147,6 +154,7 @@ TEST(Error, WarpGError)
|
|||
|
||||
TEST(Error, WarpSystemError)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with std::system_error");
|
||||
try {
|
||||
|
|
@ -166,6 +174,7 @@ TEST(Error, WarpSystemError)
|
|||
|
||||
TEST(Error, WarpExceptionPtr)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with std::exception_ptr");
|
||||
std::exception_ptr eptr;
|
||||
|
|
@ -202,6 +211,7 @@ TEST(Error, WarpExceptionPtr)
|
|||
|
||||
TEST(Error, WarpStdException)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with std::exception");
|
||||
std::invalid_argument e("Invalid argument provided");
|
||||
|
|
@ -217,6 +227,7 @@ TEST(Error, WarpStdException)
|
|||
|
||||
TEST(Error, WarpNestedError)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
auto innerFn = []() -> Result<void> {
|
||||
LINGLONG_TRACE("inner function");
|
||||
|
|
@ -242,6 +253,7 @@ TEST(Error, WarpNestedError)
|
|||
|
||||
TEST(Error, WarpErrorWithCustomCode)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with custom error code");
|
||||
return LINGLONG_ERR("Custom error occurred", 12345);
|
||||
|
|
@ -256,6 +268,7 @@ TEST(Error, WarpErrorWithCustomCode)
|
|||
|
||||
TEST(Error, WarpQStringMessage)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with QString message");
|
||||
QString errorMsg = QString("Error occurred at line %1").arg(__LINE__);
|
||||
|
|
@ -271,6 +284,7 @@ TEST(Error, WarpQStringMessage)
|
|||
|
||||
TEST(Error, WarpStdStringMessage)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with std::string message");
|
||||
std::string errorMsg = "Standard string error message";
|
||||
|
|
@ -286,6 +300,7 @@ TEST(Error, WarpStdStringMessage)
|
|||
|
||||
TEST(Error, WarpEmptyFile)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test LINGLONG_ERR with empty QFile");
|
||||
QFile emptyFile;
|
||||
|
|
@ -301,6 +316,7 @@ TEST(Error, WarpEmptyFile)
|
|||
|
||||
TEST(Error, SuccessCase)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("test successful operation");
|
||||
// 模拟成功操作
|
||||
|
|
@ -312,6 +328,7 @@ TEST(Error, SuccessCase)
|
|||
|
||||
TEST(Error, ErrorCodeEnumValues)
|
||||
{
|
||||
EnvironmentVariableGuard guard("LINYAPS_BACKTRACE", "1");
|
||||
// 测试各种错误码枚举值
|
||||
auto testErrorCode = [](ErrorCode code, const std::string &description) -> Result<void> {
|
||||
LINGLONG_TRACE("test error code : " + description);
|
||||
|
|
@ -331,3 +348,45 @@ TEST(Error, ErrorCodeEnumValues)
|
|||
ASSERT_FALSE(res3.has_value());
|
||||
ASSERT_EQ(res3.error().code(), 0); // Success 错误码为0
|
||||
}
|
||||
|
||||
TEST(Error, NoBacktrace)
|
||||
{
|
||||
auto res = []() -> Result<void> {
|
||||
LINGLONG_TRACE("trace");
|
||||
return LINGLONG_ERR("error");
|
||||
}();
|
||||
|
||||
ASSERT_FALSE(res.has_value());
|
||||
auto msg = res.error().message();
|
||||
ASSERT_FALSE(strings::contains(msg, "trace")) << msg;
|
||||
ASSERT_TRUE(strings::contains(msg, "error")) << msg;
|
||||
}
|
||||
|
||||
TEST(Error, DefaultError)
|
||||
{
|
||||
auto res1 = []() -> Result<void> {
|
||||
LINGLONG_TRACE("trace 1");
|
||||
return LINGLONG_ERR("error 1", 1);
|
||||
};
|
||||
|
||||
auto res2 = [&res1]() -> Result<void> {
|
||||
LINGLONG_TRACE("trace 2");
|
||||
return LINGLONG_ERR(res1());
|
||||
}();
|
||||
|
||||
ASSERT_FALSE(res2.has_value());
|
||||
auto msg = res2.error().message();
|
||||
ASSERT_FALSE(strings::contains(msg, "trace")) << msg;
|
||||
ASSERT_TRUE(strings::contains(msg, "error 1")) << msg;
|
||||
ASSERT_EQ(res2.error().code(), 1);
|
||||
|
||||
auto res3 = [&res1]() -> Result<void> {
|
||||
LINGLONG_TRACE("trace 3");
|
||||
return LINGLONG_ERR("error 3", res1());
|
||||
}();
|
||||
ASSERT_FALSE(res3.has_value());
|
||||
msg = res3.error().message();
|
||||
ASSERT_FALSE(strings::contains(msg, "trace")) << msg;
|
||||
ASSERT_TRUE(strings::contains(msg, "error 3")) << msg;
|
||||
ASSERT_EQ(res3.error().code(), 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,428 @@
|
|||
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "linglong/utils/error/error.h"
|
||||
#include "linglong/utils/filelock.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace linglong::utils::filelock;
|
||||
using namespace linglong::utils::error;
|
||||
|
||||
namespace {
|
||||
// Helper function to create a temporary file path
|
||||
std::filesystem::path GetTempFilePath()
|
||||
{
|
||||
return std::filesystem::temp_directory_path() / ("test_filelock.lock");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Test fixture for FileLock tests
|
||||
class FileLockTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
std::filesystem::path temp_path;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
temp_path = GetTempFilePath();
|
||||
// Ensure the file does not exist initially
|
||||
std::filesystem::remove(temp_path);
|
||||
}
|
||||
|
||||
void TearDown() override { std::filesystem::remove(temp_path); }
|
||||
};
|
||||
|
||||
// Test creating FileLock with create_if_missing = true
|
||||
TEST_F(FileLockTest, CreateWithMissingFile)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_TRUE(std::filesystem::exists(temp_path));
|
||||
|
||||
const auto &lock = std::move(result).value();
|
||||
EXPECT_EQ(lock.type(), LockType::Read); // Default type
|
||||
EXPECT_FALSE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test creating FileLock without create_if_missing when file missing
|
||||
TEST_F(FileLockTest, CreateWithoutMissingFileFails)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, false);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_THAT(result.error().message(),
|
||||
::testing::HasSubstr("open file failed: No such file or directory"));
|
||||
}
|
||||
|
||||
// Test creating FileLock on existing file
|
||||
TEST_F(FileLockTest, CreateOnExistingFile)
|
||||
{
|
||||
std::ofstream file(temp_path);
|
||||
file.close();
|
||||
|
||||
auto result = FileLock::create(temp_path, false);
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
const auto &lock = std::move(result).value();
|
||||
EXPECT_FALSE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test lock and unlock for Read lock
|
||||
TEST_F(FileLockTest, LockUnlockRead)
|
||||
{
|
||||
auto result = FileLock::create(temp_path);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
auto lock_result = lock.lock(LockType::Read);
|
||||
ASSERT_TRUE(lock_result) << lock_result.error().message();
|
||||
EXPECT_TRUE(lock.isLocked());
|
||||
EXPECT_EQ(lock.type(), LockType::Read);
|
||||
|
||||
auto unlock_result = lock.unlock();
|
||||
ASSERT_TRUE(unlock_result);
|
||||
EXPECT_FALSE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test lock and unlock for Write lock
|
||||
TEST_F(FileLockTest, LockUnlockWrite)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
auto lock_result = lock.lock(LockType::Write);
|
||||
ASSERT_TRUE(lock_result);
|
||||
EXPECT_TRUE(lock.isLocked());
|
||||
EXPECT_EQ(lock.type(), LockType::Write);
|
||||
|
||||
auto unlock_result = lock.unlock();
|
||||
ASSERT_TRUE(unlock_result);
|
||||
EXPECT_FALSE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test tryLock success
|
||||
TEST_F(FileLockTest, TryLockSuccess)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
auto try_result = lock.tryLock(LockType::Write);
|
||||
ASSERT_TRUE(try_result);
|
||||
EXPECT_TRUE(*try_result);
|
||||
EXPECT_TRUE(lock.isLocked());
|
||||
|
||||
EXPECT_TRUE(lock.unlock());
|
||||
}
|
||||
|
||||
// Test tryLock failure when already locked by another process
|
||||
TEST_F(FileLockTest, TryLockFailureInterProcess)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock1 = std::move(result).value();
|
||||
|
||||
EXPECT_TRUE(lock1.lock(LockType::Write));
|
||||
EXPECT_TRUE(lock1.isLocked());
|
||||
|
||||
const pid_t pid = fork();
|
||||
if (pid == 0) { // Child process
|
||||
auto result_child = FileLock::create(temp_path, false);
|
||||
if (!result_child) {
|
||||
_exit(1);
|
||||
}
|
||||
auto lock_child = std::move(result_child).value();
|
||||
|
||||
auto try_result = lock_child.tryLock(LockType::Write);
|
||||
if (!try_result || *try_result) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
_exit(0);
|
||||
} else if (pid > 0) { // Parent
|
||||
int status{ 0 };
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||
} else {
|
||||
FAIL() << "Fork failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Test tryLockFor with timeout success
|
||||
TEST_F(FileLockTest, TryLockForSuccess)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
auto try_for_result = lock.tryLockFor(LockType::Write, std::chrono::milliseconds(100));
|
||||
ASSERT_TRUE(try_for_result);
|
||||
EXPECT_TRUE(*try_for_result);
|
||||
EXPECT_TRUE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test tryLockFor with timeout failure
|
||||
TEST_F(FileLockTest, TryLockForFailure)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock1 = std::move(result).value();
|
||||
|
||||
EXPECT_TRUE(lock1.lock(LockType::Write));
|
||||
EXPECT_TRUE(lock1.isLocked());
|
||||
|
||||
const pid_t pid = fork();
|
||||
if (pid == 0) { // Child
|
||||
auto result_child = FileLock::create(temp_path, false);
|
||||
if (!result_child) {
|
||||
_exit(1);
|
||||
}
|
||||
auto lock_child = std::move(result_child).value();
|
||||
|
||||
auto try_for_result =
|
||||
lock_child.tryLockFor(LockType::Write, std::chrono::milliseconds(100));
|
||||
if (!try_for_result || *try_for_result) {
|
||||
_exit(1);
|
||||
}
|
||||
_exit(0);
|
||||
} else if (pid > 0) {
|
||||
int status{ 0 };
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||
} else {
|
||||
FAIL() << "Fork failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Test relock from Read to Write
|
||||
TEST_F(FileLockTest, RelockReadToWrite)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
EXPECT_TRUE(lock.lock(LockType::Read));
|
||||
EXPECT_EQ(lock.type(), LockType::Read);
|
||||
|
||||
auto relock_result = lock.relock(LockType::Write);
|
||||
ASSERT_TRUE(relock_result);
|
||||
EXPECT_EQ(lock.type(), LockType::Write);
|
||||
EXPECT_TRUE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test relock from Write to Read
|
||||
TEST_F(FileLockTest, RelockWriteToRead)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
EXPECT_TRUE(lock.lock(LockType::Write));
|
||||
EXPECT_EQ(lock.type(), LockType::Write);
|
||||
|
||||
auto relock_result = lock.relock(LockType::Read);
|
||||
ASSERT_TRUE(relock_result);
|
||||
EXPECT_EQ(lock.type(), LockType::Read);
|
||||
EXPECT_TRUE(lock.isLocked());
|
||||
}
|
||||
|
||||
// Test relock when not locked fails
|
||||
TEST_F(FileLockTest, RelockNotLocked)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
|
||||
auto relock_result = lock.relock(LockType::Write);
|
||||
// Since not locked, relock should just return OK if same type, but test behavior
|
||||
// Actually, relock assumes locked, but code checks if type == new_type, returns OK
|
||||
// But to test properly:
|
||||
EXPECT_TRUE(
|
||||
relock_result); // Since type_ is Read by default, changing to Write would attempt to lock
|
||||
// Wait, relock is for changing type while locked.
|
||||
// If not locked, it shouldn't be called, but let's see code: relock doesn't check if locked.
|
||||
// It just sets the new flock.
|
||||
// So if not locked, it would effectively lock it.
|
||||
auto lock_result = lock.lock(LockType::Read);
|
||||
ASSERT_TRUE(lock_result);
|
||||
auto relock = lock.relock(LockType::Read);
|
||||
ASSERT_TRUE(relock);
|
||||
}
|
||||
|
||||
// Test multiple read locks possible (shared)
|
||||
TEST_F(FileLockTest, MultipleReadLocks)
|
||||
{
|
||||
auto result1 = FileLock::create(temp_path);
|
||||
ASSERT_TRUE(result1);
|
||||
auto lock1 = std::move(result1).value();
|
||||
EXPECT_TRUE(lock1.lock(LockType::Read));
|
||||
|
||||
const auto pid = ::fork();
|
||||
if (pid == 0) {
|
||||
auto result2 = FileLock::create(temp_path);
|
||||
if (!result2) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
auto lock2 = std::move(result2).value();
|
||||
if (auto ret = lock2.lock(LockType::Read); !ret) {
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
_exit(0);
|
||||
} else if (pid > 0) {
|
||||
int status{ 0 };
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||
} else {
|
||||
FAIL() << "Fork failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Test write lock exclusive
|
||||
TEST_F(FileLockTest, WriteLockExclusive)
|
||||
{
|
||||
auto result1 = FileLock::create(temp_path);
|
||||
ASSERT_TRUE(result1);
|
||||
auto lock1 = std::move(result1).value();
|
||||
EXPECT_TRUE(lock1.lock(LockType::Write));
|
||||
|
||||
const auto pid = ::fork();
|
||||
if (pid == 0) {
|
||||
auto result2 = FileLock::create(temp_path, false);
|
||||
if (!result2) {
|
||||
_exit(1);
|
||||
}
|
||||
auto lock2 = std::move(result2).value();
|
||||
|
||||
auto try_read = lock2.tryLock(LockType::Read);
|
||||
if (!try_read || *try_read) {
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
auto try_write = lock2.tryLock(LockType::Write);
|
||||
if (!try_write || *try_write) {
|
||||
_exit(3);
|
||||
}
|
||||
|
||||
_exit(0);
|
||||
} else if (pid > 0) {
|
||||
int status{ 0 };
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||
} else {
|
||||
FAIL() << "Fork failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Test behavior after fork
|
||||
TEST_F(FileLockTest, LockAfterFork)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
EXPECT_TRUE(lock.lock(LockType::Write));
|
||||
|
||||
const pid_t pid = fork();
|
||||
if (pid == 0) { // Child
|
||||
// In child, lock should not be considered locked because pid changed
|
||||
if (lock.isLocked()) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
auto lock_result = lock.lock(LockType::Write);
|
||||
if (lock_result) { // Should fail because "cannot operate on lock from forked process"
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
_exit(0);
|
||||
} else if (pid > 0) {
|
||||
int status{ 0 };
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||
EXPECT_TRUE(lock.isLocked()); // Parent still locked
|
||||
} else {
|
||||
FAIL() << "Fork failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Test cannot create duplicate lock in same process
|
||||
TEST_F(FileLockTest, NoDuplicateLockSameProcess)
|
||||
{
|
||||
auto result1 = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result1);
|
||||
|
||||
auto result2 = FileLock::create(temp_path, false);
|
||||
ASSERT_FALSE(result2);
|
||||
EXPECT_THAT(result2.error().message(),
|
||||
::testing::HasSubstr("process already holds a lock on file"));
|
||||
}
|
||||
|
||||
// Test move constructor
|
||||
TEST_F(FileLockTest, MoveConstructor)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock1 = std::move(result).value();
|
||||
EXPECT_TRUE(lock1.lock(LockType::Write));
|
||||
|
||||
auto lock2(std::move(lock1));
|
||||
EXPECT_TRUE(lock2.isLocked());
|
||||
EXPECT_FALSE(lock1.isLocked()); // Moved from
|
||||
}
|
||||
|
||||
// Test move assignment
|
||||
TEST_F(FileLockTest, MoveAssignment)
|
||||
{
|
||||
auto result1 = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result1);
|
||||
auto lock1 = std::move(result1).value();
|
||||
EXPECT_TRUE(lock1.lock(LockType::Write));
|
||||
|
||||
const std::filesystem::path other_path =
|
||||
std::filesystem::temp_directory_path() / ("other_" + std::to_string(::getpid()) + ".lock");
|
||||
auto result2 = FileLock::create(other_path, true);
|
||||
ASSERT_TRUE(result2);
|
||||
auto lock2 = std::move(result2).value();
|
||||
|
||||
lock2 = std::move(lock1);
|
||||
EXPECT_TRUE(lock2.isLocked());
|
||||
EXPECT_EQ(lock2.type(), LockType::Write);
|
||||
EXPECT_FALSE(lock1.isLocked());
|
||||
}
|
||||
|
||||
// Test tryLock when already locked same type
|
||||
TEST_F(FileLockTest, TryLockAlreadyLockedSameType)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
EXPECT_TRUE(lock.lock(LockType::Read));
|
||||
|
||||
auto try_result = lock.tryLock(LockType::Read);
|
||||
ASSERT_TRUE(try_result);
|
||||
}
|
||||
|
||||
// Test lock when already locked different type
|
||||
TEST_F(FileLockTest, LockAlreadyLockedDiffType)
|
||||
{
|
||||
auto result = FileLock::create(temp_path, true);
|
||||
ASSERT_TRUE(result);
|
||||
auto lock = std::move(result).value();
|
||||
EXPECT_TRUE(lock.lock(LockType::Read));
|
||||
|
||||
auto lock_result = lock.lock(LockType::Write);
|
||||
ASSERT_FALSE(lock_result);
|
||||
EXPECT_THAT(lock_result.error().message(),
|
||||
::testing::HasSubstr("use relock to change lock type"));
|
||||
}
|
||||
|
|
@ -769,11 +769,12 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
.type = "tmpfs" });
|
||||
|
||||
auto containerHome = homePath->string();
|
||||
|
||||
homeMount->emplace_back(Mount{ .destination = containerHome,
|
||||
.options = string_list{ "rbind" },
|
||||
.source = *homePath,
|
||||
.type = "bind" });
|
||||
if (!skipHostHome) {
|
||||
homeMount->emplace_back(Mount{ .destination = containerHome,
|
||||
.options = string_list{ "rbind" },
|
||||
.source = *homePath,
|
||||
.type = "bind" });
|
||||
}
|
||||
environment["HOME"] = containerHome;
|
||||
|
||||
auto mountDir = [this](const std::filesystem::path &hostDir, const std::string &containerDir) {
|
||||
|
|
@ -803,7 +804,7 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
value ? std::filesystem::path{ value } : *homePath / ".local" / "share";
|
||||
std::string containerDataHome = containerHome + "/.local/share";
|
||||
if (XDG_DATA_HOME != containerDataHome) {
|
||||
if (!mountDir(XDG_DATA_HOME, containerDataHome)) {
|
||||
if (!skipHostHome && !mountDir(XDG_DATA_HOME, containerDataHome)) {
|
||||
error_.reason = XDG_DATA_HOME.string() + " can't be mount";
|
||||
error_.code = BUILD_MOUNT_HOME_ERROR;
|
||||
return false;
|
||||
|
|
@ -834,7 +835,7 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
}
|
||||
std::string containerConfigHome = containerHome + "/.config";
|
||||
if (XDGConfigHome != containerConfigHome) {
|
||||
if (!mountDir(XDGConfigHome, containerConfigHome)) {
|
||||
if (!skipHostHome && !mountDir(XDGConfigHome, containerConfigHome)) {
|
||||
error_.reason = XDGConfigHome.string() + " can't be mount";
|
||||
error_.code = BUILD_MOUNT_HOME_ERROR;
|
||||
return false;
|
||||
|
|
@ -852,7 +853,7 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
}
|
||||
std::string containerCacheHome = containerHome + "/.cache";
|
||||
if (XDGCacheHome != containerCacheHome) {
|
||||
if (!mountDir(XDGCacheHome, containerCacheHome)) {
|
||||
if (!skipHostHome && !mountDir(XDGCacheHome, containerCacheHome)) {
|
||||
error_.reason = XDGCacheHome.string() + " can't be mount";
|
||||
error_.code = BUILD_MOUNT_HOME_ERROR;
|
||||
return false;
|
||||
|
|
@ -869,7 +870,7 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
}
|
||||
std::string containerStateHome = containerHome + "/.local/state";
|
||||
if (XDG_STATE_HOME != containerStateHome) {
|
||||
if (!mountDir(XDG_STATE_HOME, containerStateHome)) {
|
||||
if (!skipHostHome && !mountDir(XDG_STATE_HOME, containerStateHome)) {
|
||||
error_.reason = XDG_STATE_HOME.string() + " can't be mount";
|
||||
error_.code = BUILD_MOUNT_HOME_ERROR;
|
||||
return false;
|
||||
|
|
@ -881,10 +882,12 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
auto hostSystemdUserDir = XDG_CONFIG_HOME / "systemd" / "user";
|
||||
if ((XDG_CONFIG_HOME != containerConfigHome)
|
||||
&& std::filesystem::exists(hostSystemdUserDir, ec)) {
|
||||
homeMount->emplace_back(Mount{ .destination = containerConfigHome + "/systemd/user",
|
||||
if (!skipHostHome) {
|
||||
homeMount->emplace_back(Mount{ .destination = containerConfigHome + "/systemd/user",
|
||||
.options = string_list{ "rbind" },
|
||||
.source = hostSystemdUserDir,
|
||||
.type = "bind" });
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Many applications get configurations from dconf, so we expose dconf to all
|
||||
|
|
@ -893,15 +896,18 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
auto hostUserDconfPath = XDG_CONFIG_HOME / "dconf";
|
||||
if ((XDG_CONFIG_HOME != containerConfigHome)
|
||||
&& std::filesystem::exists(hostUserDconfPath, ec)) {
|
||||
homeMount->emplace_back(Mount{ .destination = containerConfigHome + "/dconf",
|
||||
if (!skipHostHome) {
|
||||
homeMount->emplace_back(Mount{ .destination = containerConfigHome + "/dconf",
|
||||
.options = string_list{ "rbind" },
|
||||
.source = hostUserDconfPath,
|
||||
.type = "bind" });
|
||||
}
|
||||
}
|
||||
|
||||
// for dde application theme
|
||||
auto hostDDEApiPath = XDG_CACHE_HOME / "deepin" / "dde-api";
|
||||
if ((XDG_CACHE_HOME != containerCacheHome) && std::filesystem::exists(hostDDEApiPath, ec)) {
|
||||
if (!skipHostHome && (XDG_CACHE_HOME != containerCacheHome)
|
||||
&& std::filesystem::exists(hostDDEApiPath, ec)) {
|
||||
homeMount->emplace_back(Mount{ .destination = containerCacheHome + "/deepin/dde-api",
|
||||
.options = string_list{ "rbind" },
|
||||
.source = hostDDEApiPath,
|
||||
|
|
@ -910,7 +916,8 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
|
||||
// for xdg-user-dirs
|
||||
auto XDGUserDirs = XDG_CONFIG_HOME / "user-dirs.dirs";
|
||||
if ((XDG_CONFIG_HOME != containerConfigHome) && std::filesystem::exists(XDGUserDirs, ec)) {
|
||||
if (!skipHostHome && (XDG_CONFIG_HOME != containerConfigHome)
|
||||
&& std::filesystem::exists(XDGUserDirs, ec)) {
|
||||
homeMount->push_back(Mount{ .destination = containerConfigHome + "/user-dirs.dirs",
|
||||
.options = string_list{ "rbind" },
|
||||
.source = XDGUserDirs,
|
||||
|
|
@ -918,7 +925,8 @@ bool ContainerCfgBuilder::buildMountHome() noexcept
|
|||
}
|
||||
|
||||
auto XDGUserLocale = XDG_CONFIG_HOME / "user-dirs.locale";
|
||||
if ((XDG_CONFIG_HOME != containerConfigHome) && std::filesystem::exists(XDGUserLocale, ec)) {
|
||||
if (!skipHostHome && (XDG_CONFIG_HOME != containerConfigHome)
|
||||
&& std::filesystem::exists(XDGUserLocale, ec)) {
|
||||
homeMount->push_back(Mount{ .destination = containerConfigHome + "/user-dirs.locale",
|
||||
.options = string_list{ "rbind" },
|
||||
.source = XDGUserLocale,
|
||||
|
|
|
|||
|
|
@ -143,6 +143,11 @@ public:
|
|||
ContainerCfgBuilder &bindHostRoot() noexcept;
|
||||
ContainerCfgBuilder &bindHostStatics() noexcept;
|
||||
ContainerCfgBuilder &bindHome(std::filesystem::path hostHome) noexcept;
|
||||
ContainerCfgBuilder &disableHostHomeBind() noexcept
|
||||
{
|
||||
skipHostHome = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ContainerCfgBuilder &bindXOrgSocket(const std::filesystem::path &socket) noexcept;
|
||||
ContainerCfgBuilder &bindXAuthFile(const std::filesystem::path &authFile) noexcept;
|
||||
|
|
@ -304,6 +309,7 @@ private:
|
|||
// home dir
|
||||
std::optional<std::filesystem::path> homePath;
|
||||
std::optional<std::vector<ocppi::runtime::config::types::Mount>> homeMount;
|
||||
bool skipHostHome{ false };
|
||||
|
||||
// private dir
|
||||
std::filesystem::path privatePath;
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ pfl_add_library(
|
|||
STATIC
|
||||
SOURCES
|
||||
# find -regex '\.\/*.+\.[ch]\(pp\)?\(.in\)?' -type f -printf '%P\n'| sort
|
||||
src/linglong/utils/command/env.cpp
|
||||
src/linglong/utils/command/env.h
|
||||
src/linglong/utils/bash_command_helper.h
|
||||
src/linglong/utils/command/cmd.cpp
|
||||
src/linglong/utils/command/cmd.h
|
||||
src/linglong/utils/command/env.cpp
|
||||
src/linglong/utils/command/env.h
|
||||
src/linglong/utils/command/ocppi-helper.cpp
|
||||
src/linglong/utils/command/ocppi-helper.h
|
||||
src/linglong/utils/dbus/log.cpp
|
||||
|
|
@ -25,11 +26,18 @@ pfl_add_library(
|
|||
src/linglong/utils/error/details/error_impl.h
|
||||
src/linglong/utils/error/error.cpp
|
||||
src/linglong/utils/error/error.h
|
||||
src/linglong/utils/file.cpp
|
||||
src/linglong/utils/file.h
|
||||
src/linglong/utils/filelock.cpp
|
||||
src/linglong/utils/filelock.h
|
||||
src/linglong/utils/finally/finally.cpp
|
||||
src/linglong/utils/finally/finally.h
|
||||
src/linglong/utils/gettext.h
|
||||
src/linglong/utils/gkeyfile_wrapper.h
|
||||
src/linglong/utils/global/initialize.cpp
|
||||
src/linglong/utils/global/initialize.h
|
||||
src/linglong/utils/hooks.cpp
|
||||
src/linglong/utils/hooks.h
|
||||
src/linglong/utils/log/formatter.cpp
|
||||
src/linglong/utils/log/formatter.h
|
||||
src/linglong/utils/log/log.cpp
|
||||
|
|
@ -46,14 +54,8 @@ pfl_add_library(
|
|||
src/linglong/utils/serialize/yaml.h
|
||||
src/linglong/utils/transaction.cpp
|
||||
src/linglong/utils/transaction.h
|
||||
src/linglong/utils/file.cpp
|
||||
src/linglong/utils/file.h
|
||||
src/linglong/utils/xdg/directory.h
|
||||
src/linglong/utils/xdg/directory.cpp
|
||||
src/linglong/utils/gkeyfile_wrapper.h
|
||||
src/linglong/utils/hooks.cpp
|
||||
src/linglong/utils/hooks.h
|
||||
src/linglong/utils/bash_command_helper.h
|
||||
src/linglong/utils/xdg/directory.h
|
||||
COMPILE_FEATURES
|
||||
PUBLIC
|
||||
cxx_std_17
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -18,14 +21,17 @@ public:
|
|||
ErrorImpl(const char *file,
|
||||
int line,
|
||||
int code,
|
||||
std::string msg,
|
||||
std::string trace_msg,
|
||||
std::optional<std::string> msg,
|
||||
std::unique_ptr<ErrorImpl> cause = nullptr)
|
||||
: _code(code)
|
||||
, _msg(std::move(msg))
|
||||
, _file(file)
|
||||
, _line(line)
|
||||
, cause(std::move(cause))
|
||||
, _trace_msg(std::move(trace_msg))
|
||||
{
|
||||
backtrace = (getenv("LINYAPS_BACKTRACE") != nullptr);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto code() const -> int { return _code; };
|
||||
|
|
@ -37,17 +43,31 @@ public:
|
|||
if (!msg.empty()) {
|
||||
msg += "\n";
|
||||
}
|
||||
msg += err->_file + ":" + std::to_string(err->_line) + " " + err->_msg;
|
||||
if (backtrace) {
|
||||
auto trace_msg = err->_trace_msg;
|
||||
if (err->_msg) {
|
||||
trace_msg += ": " + *err->_msg;
|
||||
}
|
||||
|
||||
msg += fmt::format("{}:{} {}", err->_file, err->_line, trace_msg);
|
||||
} else {
|
||||
if (err->_msg) {
|
||||
msg = *err->_msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
private:
|
||||
int _code;
|
||||
std::string _msg;
|
||||
std::optional<std::string> _msg;
|
||||
std::string _file;
|
||||
int _line;
|
||||
std::unique_ptr<ErrorImpl> cause;
|
||||
std::string _trace_msg;
|
||||
bool backtrace = false;
|
||||
};
|
||||
|
||||
} // namespace linglong::utils::error::details
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
static_cast<int>(code),
|
||||
trace_msg + ": " + msg.toStdString(),
|
||||
trace_msg,
|
||||
msg.toStdString(),
|
||||
nullptr));
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +97,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
static_cast<int>(code),
|
||||
trace_msg + ": " + msg,
|
||||
trace_msg,
|
||||
msg,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +111,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
static_cast<int>(code),
|
||||
trace_msg + ": " + msg,
|
||||
trace_msg,
|
||||
msg,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +123,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
code,
|
||||
trace_msg + ": " + msg.toStdString(),
|
||||
trace_msg,
|
||||
msg.toStdString(),
|
||||
nullptr));
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +135,7 @@ public:
|
|||
int code = -1) -> Error
|
||||
{
|
||||
return Error(
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg + ": " + msg, nullptr));
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, msg, nullptr));
|
||||
}
|
||||
|
||||
static auto
|
||||
|
|
@ -139,7 +143,7 @@ public:
|
|||
-> Error
|
||||
{
|
||||
return Error(
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg + ": " + msg, nullptr));
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, msg, nullptr));
|
||||
}
|
||||
|
||||
static auto Err(const char *file,
|
||||
|
|
@ -148,12 +152,13 @@ public:
|
|||
const QString &msg,
|
||||
const QFile &qfile) -> Error
|
||||
{
|
||||
return Error(std::make_unique<details::ErrorImpl>(
|
||||
file,
|
||||
line,
|
||||
qfile.error(),
|
||||
trace_msg + ": " + msg.toStdString() + ": " + qfile.errorString().toStdString(),
|
||||
nullptr));
|
||||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
qfile.error(),
|
||||
trace_msg,
|
||||
msg.toStdString() + ": "
|
||||
+ qfile.errorString().toStdString(),
|
||||
nullptr));
|
||||
}
|
||||
|
||||
static auto Err(const char *file, int line, const std::string &trace_msg, const QFile &qfile)
|
||||
|
|
@ -162,8 +167,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
qfile.error(),
|
||||
trace_msg + ": "
|
||||
+ qfile.fileName().toStdString() + ": "
|
||||
trace_msg,
|
||||
qfile.fileName().toStdString() + ": "
|
||||
+ qfile.errorString().toStdString(),
|
||||
nullptr));
|
||||
}
|
||||
|
|
@ -174,16 +179,17 @@ public:
|
|||
std::exception_ptr err,
|
||||
int code = -1) -> Error
|
||||
{
|
||||
std::string what = trace_msg + ": ";
|
||||
std::string what;
|
||||
try {
|
||||
std::rethrow_exception(std::move(err));
|
||||
} catch (const std::exception &e) {
|
||||
what += e.what();
|
||||
what = e.what();
|
||||
} catch (...) {
|
||||
what += "unknown";
|
||||
what = "unknown";
|
||||
}
|
||||
|
||||
return Error(std::make_unique<details::ErrorImpl>(file, line, code, what, nullptr));
|
||||
return Error(
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, what, nullptr));
|
||||
}
|
||||
|
||||
static auto Err(const char *file,
|
||||
|
|
@ -193,7 +199,7 @@ public:
|
|||
std::exception_ptr err,
|
||||
int code = -1) -> Error
|
||||
{
|
||||
std::string what = trace_msg + ": " + msg.toStdString() + ": ";
|
||||
std::string what = msg.toStdString() + ": ";
|
||||
try {
|
||||
std::rethrow_exception(std::move(err));
|
||||
} catch (const std::exception &e) {
|
||||
|
|
@ -202,18 +208,15 @@ public:
|
|||
what += "unknown";
|
||||
}
|
||||
|
||||
return Error(std::make_unique<details::ErrorImpl>(file, line, code, what, nullptr));
|
||||
return Error(
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, what, nullptr));
|
||||
}
|
||||
|
||||
static auto
|
||||
Err(const char *file, int line, const std::string &trace_msg, const std::exception &e) -> Error
|
||||
{
|
||||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
|
||||
-1,
|
||||
trace_msg + ": " + e.what(),
|
||||
nullptr));
|
||||
return Error(
|
||||
std::make_unique<details::ErrorImpl>(file, line, -1, trace_msg, e.what(), nullptr));
|
||||
}
|
||||
|
||||
static auto Err(const char *file,
|
||||
|
|
@ -223,9 +226,10 @@ public:
|
|||
const std::exception &e,
|
||||
int code = -1) -> Error
|
||||
{
|
||||
std::string what = trace_msg + ": " + msg.toStdString() + ": " + e.what();
|
||||
std::string what = msg.toStdString() + ": " + e.what();
|
||||
|
||||
return Error(std::make_unique<details::ErrorImpl>(file, line, code, what, nullptr));
|
||||
return Error(
|
||||
std::make_unique<details::ErrorImpl>(file, line, code, trace_msg, what, nullptr));
|
||||
}
|
||||
|
||||
static auto Err(const char *file,
|
||||
|
|
@ -281,7 +285,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
cause.error().code(),
|
||||
trace_msg + ": " + msg.toStdString(),
|
||||
trace_msg,
|
||||
msg.toStdString(),
|
||||
std::move(cause.error().pImpl)));
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +302,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
cause.error().code(),
|
||||
trace_msg + ": " + msg,
|
||||
trace_msg,
|
||||
msg,
|
||||
std::move(cause.error().pImpl)));
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +319,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
cause.error().code(),
|
||||
trace_msg + ": " + msg,
|
||||
trace_msg,
|
||||
msg,
|
||||
std::move(cause.error().pImpl)));
|
||||
}
|
||||
|
||||
|
|
@ -329,6 +336,7 @@ public:
|
|||
line,
|
||||
cause.error().code(),
|
||||
trace_msg,
|
||||
std::nullopt,
|
||||
std::move(cause.error().pImpl)));
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +349,8 @@ public:
|
|||
return Error(std::make_unique<details::ErrorImpl>(file,
|
||||
line,
|
||||
cause.code(),
|
||||
trace_msg + ": " + msg,
|
||||
trace_msg,
|
||||
msg,
|
||||
std::move(cause.pImpl)));
|
||||
}
|
||||
|
||||
|
|
@ -353,6 +362,7 @@ public:
|
|||
line,
|
||||
cause.code(),
|
||||
trace_msg,
|
||||
std::nullopt,
|
||||
std::move(cause.pImpl)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "linglong/utils/filelock.h"
|
||||
|
||||
#include "linglong/common/constants.h"
|
||||
#include "linglong/utils/finally/finally.h"
|
||||
#include "linglong/utils/log/log.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace linglong::utils::filelock {
|
||||
|
||||
pid_t FileLock::pid() const noexcept
|
||||
{
|
||||
return pid_;
|
||||
}
|
||||
|
||||
utils::error::Result<FileLock> FileLock::create(std::filesystem::path path,
|
||||
bool create_if_missing) noexcept
|
||||
{
|
||||
LINGLONG_TRACE("create file lock");
|
||||
|
||||
auto cur_pid = ::getpid();
|
||||
auto &[global_pid, locked_paths] = process_locked_paths;
|
||||
if (global_pid != cur_pid) {
|
||||
global_pid = cur_pid;
|
||||
locked_paths.clear();
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
auto abs_path = std::filesystem::absolute(path, ec);
|
||||
if (ec) {
|
||||
return LINGLONG_ERR(fmt::format("canonicalize path {} error: {}", path, ec.message()));
|
||||
}
|
||||
|
||||
if (locked_paths.find(abs_path) != locked_paths.end()) {
|
||||
return LINGLONG_ERR(fmt::format("process already holds a lock on file {}", abs_path));
|
||||
}
|
||||
|
||||
unsigned int flags = O_RDWR | O_CLOEXEC | O_NOFOLLOW;
|
||||
if (create_if_missing) {
|
||||
flags |= O_CREAT;
|
||||
}
|
||||
|
||||
auto fd = ::open(abs_path.c_str(), flags, default_file_mode);
|
||||
if (fd < 0) {
|
||||
return LINGLONG_ERR(fmt::format("open file failed: {}", ::strerror(errno)));
|
||||
}
|
||||
|
||||
locked_paths[abs_path] = true;
|
||||
FileLock lock(fd, std::move(abs_path));
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
FileLock::~FileLock() noexcept
|
||||
{
|
||||
if (isLocked()) {
|
||||
auto ret = unlock();
|
||||
if (!ret) {
|
||||
LogW("unlock file failed: {}", ret.error());
|
||||
}
|
||||
}
|
||||
|
||||
if (fd > 0 && ::close(fd) < 0) {
|
||||
LogW("close file failed: {}", ::strerror(errno));
|
||||
}
|
||||
|
||||
fd = -1;
|
||||
auto it = process_locked_paths.second.find(path);
|
||||
if (it != process_locked_paths.second.end()) {
|
||||
process_locked_paths.second.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
FileLock::FileLock(int fd, std::filesystem::path path) noexcept
|
||||
: fd(fd)
|
||||
, path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
FileLock::FileLock(FileLock &&other) noexcept
|
||||
: type_(other.type_)
|
||||
, fd(other.fd)
|
||||
, path(std::move(other.path))
|
||||
{
|
||||
if (other.pid_ != pid()) {
|
||||
LogF("move lock to different process");
|
||||
}
|
||||
|
||||
locked.store(other.locked.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||
other.locked.store(false, std::memory_order_relaxed);
|
||||
other.fd = -1;
|
||||
}
|
||||
|
||||
FileLock &FileLock::operator=(FileLock &&other) noexcept
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (other.pid_ != pid()) {
|
||||
LogF("move lock to different process");
|
||||
}
|
||||
|
||||
fd = other.fd;
|
||||
path = std::move(other.path);
|
||||
type_ = other.type_;
|
||||
|
||||
locked.store(other.locked.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||
other.locked.store(false, std::memory_order_relaxed);
|
||||
other.fd = -1;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
utils::error::Result<void> FileLock::lockCheck() const noexcept
|
||||
{
|
||||
LINGLONG_TRACE("validating file lock");
|
||||
|
||||
if (pid_ != ::getpid()) {
|
||||
return LINGLONG_ERR("cannot operate on lock from forked process");
|
||||
}
|
||||
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
utils::error::Result<void> FileLock::lock(LockType type) noexcept
|
||||
{
|
||||
LINGLONG_TRACE("lock file");
|
||||
|
||||
auto ret = lockCheck();
|
||||
if (!ret) {
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
|
||||
if (isLocked()) {
|
||||
if (type == type_) {
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
return LINGLONG_ERR(
|
||||
fmt::format("use relock to change lock type from {} to {}", type_, type));
|
||||
}
|
||||
|
||||
struct flock fl{};
|
||||
fl.l_type = (type == LockType::Write) ? F_WRLCK : F_RDLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
|
||||
while (true) {
|
||||
if (::fcntl(fd, F_SETLKW, &fl) == 0) {
|
||||
type_ = type;
|
||||
locked.store(true, std::memory_order_relaxed);
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return LINGLONG_ERR(fmt::format("failed to lock file {}: {}", path, ::strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
utils::error::Result<bool> FileLock::tryLock(LockType type) noexcept
|
||||
{
|
||||
LINGLONG_TRACE("try lock file");
|
||||
|
||||
auto ret = lockCheck();
|
||||
if (!ret) {
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
|
||||
if (isLocked()) {
|
||||
if (type == type_) {
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
return LINGLONG_ERR(
|
||||
fmt::format("use relock to change lock type from {} to {}", type_, type));
|
||||
}
|
||||
|
||||
struct flock fl{};
|
||||
fl.l_type = (type == LockType::Write) ? F_WRLCK : F_RDLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
|
||||
while (true) {
|
||||
if (::fcntl(fd, F_SETLK, &fl) == 0) {
|
||||
type_ = type;
|
||||
locked.store(true, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (errno == EACCES || errno == EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return LINGLONG_ERR(fmt::format("failed to lock file {}: {}", path, ::strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
utils::error::Result<void> FileLock::unlock() noexcept
|
||||
{
|
||||
LINGLONG_TRACE("unlock file");
|
||||
|
||||
auto ret = lockCheck();
|
||||
if (!ret) {
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
|
||||
if (!isLocked()) {
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
struct flock fl{};
|
||||
fl.l_type = F_UNLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
|
||||
while (true) {
|
||||
if (::fcntl(fd, F_SETLK, &fl) == 0) {
|
||||
locked.store(false, std::memory_order_relaxed);
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return LINGLONG_ERR(fmt::format("failed to unlock file {}: {}", path, ::strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
utils::error::Result<void> FileLock::relock(LockType new_type) noexcept
|
||||
{
|
||||
LINGLONG_TRACE("upgrade lock type");
|
||||
|
||||
if (type_ == new_type) {
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
auto ret = lockCheck();
|
||||
if (!ret) {
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
|
||||
struct flock fl{};
|
||||
fl.l_type = (new_type == LockType::Write) ? F_WRLCK : F_RDLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
|
||||
while (true) {
|
||||
if (::fcntl(fd, F_SETLKW, &fl) == 0) {
|
||||
type_ = new_type;
|
||||
return LINGLONG_OK;
|
||||
}
|
||||
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return LINGLONG_ERR(fmt::format("failed to relock file {}: {}", path, ::strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
bool FileLock::isLocked() const noexcept
|
||||
{
|
||||
return locked.load(std::memory_order_relaxed) && pid() == ::getpid();
|
||||
}
|
||||
|
||||
} // namespace linglong::utils::filelock
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "linglong/utils/error/error.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace linglong::utils::filelock {
|
||||
|
||||
enum class LockType : uint8_t {
|
||||
Read,
|
||||
Write,
|
||||
};
|
||||
|
||||
class FileLock
|
||||
{
|
||||
public:
|
||||
static utils::error::Result<FileLock> create(std::filesystem::path path,
|
||||
bool create_if_missing = true) noexcept;
|
||||
|
||||
~FileLock() noexcept;
|
||||
|
||||
FileLock(const FileLock &) = delete;
|
||||
FileLock &operator=(const FileLock &) = delete;
|
||||
FileLock(FileLock &&) noexcept;
|
||||
FileLock &operator=(FileLock &&) noexcept;
|
||||
|
||||
utils::error::Result<void> lock(LockType type) noexcept;
|
||||
|
||||
utils::error::Result<bool> tryLock(LockType type) noexcept;
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
utils::error::Result<bool>
|
||||
tryLockFor(LockType type,
|
||||
const std::chrono::duration<Rep, Period> &timeout = std::chrono::milliseconds(100),
|
||||
const std::chrono::milliseconds &poll_interval = std::chrono::milliseconds(10))
|
||||
{
|
||||
LINGLONG_TRACE("try lock with timeout");
|
||||
|
||||
auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
auto ret = tryLock(type);
|
||||
if (!ret) {
|
||||
return LINGLONG_ERR(ret);
|
||||
}
|
||||
if (*ret) {
|
||||
return true;
|
||||
}
|
||||
std::this_thread::sleep_for(poll_interval);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
utils::error::Result<void> relock(LockType new_type) noexcept;
|
||||
|
||||
utils::error::Result<void> unlock() noexcept;
|
||||
|
||||
[[nodiscard]] LockType type() const noexcept { return type_; }
|
||||
|
||||
[[nodiscard]] bool isLocked() const noexcept;
|
||||
|
||||
[[nodiscard]] pid_t pid() const noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] utils::error::Result<void> lockCheck() const noexcept;
|
||||
|
||||
FileLock(int fd, std::filesystem::path path) noexcept;
|
||||
|
||||
pid_t pid_{ ::getpid() };
|
||||
LockType type_{ LockType::Read };
|
||||
std::atomic_bool locked{ false };
|
||||
int fd{ -1 };
|
||||
std::filesystem::path path;
|
||||
|
||||
static inline std::pair<pid_t, std::unordered_map<std::string, bool>> process_locked_paths{
|
||||
::getpid(), {}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace linglong::utils::filelock
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<linglong::utils::filelock::LockType>
|
||||
{
|
||||
constexpr auto parse(fmt::format_parse_context &ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const linglong::utils::filelock::LockType &p, FormatContext &ctx) const
|
||||
{
|
||||
const auto *msg = p == linglong::utils::filelock::LockType::Read ? "read" : "write";
|
||||
return fmt::format_to(ctx.out(), "{}", msg);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue