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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ system.run_in_script(script, true) -- admin(sudo)同理

旧版本的 `system.exec` 忽略 `tty`,而它本来就是直通终端,所以 recipe 可以无条件采用。直通用的原始 `os.execute` 只以内部名 `_LIBXPKG_EXEC_INHERIT` 暴露,不是公开的 `os.*` 函数。

**需要管理员权限的步骤:声明,不执行(0.0.63)。** recipe 不自己 `sudo`:无人值守的安装里没有终端可问,
等待会一直挂住;有人在场时,问谁、何时问、记录在哪里,应由宿主统一决定。用 `system.admin_step(argv, why)`
声明一步(`argv` 是命令的词列表,`why` 是一行给人看的理由),它作为延后请求交给宿主
(`install_requests()` 里 `op == "admin"`,`target` 是 why 加上各个词,以 ASCII 单元分隔符 0x1F 连接)。
宿主在安装结束后一次性询问并执行,没人可问时记为待办。旧版本没有这个函数,recipe 先判断再用:

```lua
if system.admin_step then
system.admin_step({"chown", "root:root", sandbox}, "Chromium's sandbox helper must be root's")
else
log.warn("run as root: chown root:root %s && chmod 4755 %s", sandbox, sandbox)
end
```

`system.run_in_script(script, true)` 仍直接经 `sudo` 执行,只为已有 recipe 保留;新 recipe 用 `admin_step`。

#### `pkginfo.build_dep` 的名字规范化(0.0.60)

xlings 导出 `XLINGS_BUILDDEP_<NAME>_PATH` 时先去掉 `@版本`,再去掉命名空间前缀,再把字母数字转大写、其余转 `_`。`build_dep` 现在按同一规则拼变量名,`build_dep("xim:7zip")`、`build_dep("7zip")`、`build_dep("xim:7zip@26.02")` 读的是同一个 `XLINGS_BUILDDEP_7ZIP_PATH`。之前带命名空间的写法查的是 `XLINGS_BUILDDEP_XIM_7ZIP_PATH`,永远查不到,退回 `dep_install_dir`(它不记录 build 依赖),最后返回 nil。查不到变量时仍退回 `dep_install_dir`。
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "xpkg"
version = "0.0.62"
version = "0.0.63"
description = "C++23 reference implementation of the xpkg V2 spec (multi-arch)"
license = "Apache-2.0"
repo = "https://github.com/openxlings/libxpkg"
Expand Down
19 changes: 19 additions & 0 deletions src/lua-stdlib/xim/libxpkg/system.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@ function M.exec(cmd, opt)
end
end

-- A step only an administrator may take (chown root a sandbox helper, a
-- setuid bit), DECLARED, never run here: the host decides who is asked, when
-- and whether anyone can be (a recipe's own `sudo` waits on a terminal that
-- an unattended install does not have). `argv` is the command as a list,
-- `why` one line a person reads before saying yes. It reaches the host as a
-- deferred request: op "admin", target = why, then each word, separated by
-- the ASCII unit separator (0x1F).
function M.admin_step(argv, why)
if type(argv) ~= "table" or #argv == 0 then error("system.admin_step: argv is a non-empty list") end
local words = { tostring(why or "") }
for _, w in ipairs(argv) do
w = tostring(w)
if w:find("\31", 1, true) then error("system.admin_step: a word holds the separator") end
table.insert(words, w)
end
if not _INSTALL_REQUESTS then _INSTALL_REQUESTS = {} end
table.insert(_INSTALL_REQUESTS, {op = "admin", target = table.concat(words, "\31")})
end

function M.rundir() return _RUNTIME and _RUNTIME.run_dir or nil end
function M.xpkgdir() return _RUNTIME and _RUNTIME.xpkg_dir or nil end
function M.bindir() return _RUNTIME and _RUNTIME.bin_dir or nil end
Expand Down
5 changes: 3 additions & 2 deletions src/xpkg-executor.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,9 @@ struct XvmOp {
};

struct InstallRequest {
std::string op; // "install" | "remove"
std::string target; // e.g. "scode:linux-headers@5.11.1"
std::string op; // "install" | "remove" | "admin" (system.admin_step)
std::string target; // e.g. "scode:linux-headers@5.11.1"; for "admin": why, then
// each word of the command, joined by '\x1f'
};

enum class HookType { Installed, Build, Install, Config, Uninstall };
Expand Down
22 changes: 22 additions & 0 deletions tests/test_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3760,6 +3760,28 @@ TEST(ExecutionBoundaryTest, TheLegacyFactoryStillLoadsAndRunsLocally) {
fs::remove_all(temp);
}

TEST(ExecutorTest, AnAdminStepIsDeclaredNotRun) {
const auto temp = make_temp_dir("libxpkg-admin-step-");
const auto recipe = temp / "admin.lua";
const auto marker = temp / "ran";
write_text(recipe, std::string(R"LUA(
import('xim.libxpkg.system')
function install()
system.admin_step({'touch', ')LUA") + marker.string() + R"LUA('}, 'a file only root may make')
return true
end
)LUA");
auto exec = create_executor(recipe);
ASSERT_TRUE(exec) << exec.error();
ASSERT_TRUE(exec->run_hook(HookType::Install, make_context(temp / "payload", "linux")).success);
EXPECT_FALSE(fs::exists(marker)) << "declared, never run by the recipe";
auto requests = exec->install_requests();
ASSERT_EQ(requests.size(), 1u);
EXPECT_EQ(requests[0].op, "admin");
EXPECT_EQ(requests[0].target, "a file only root may make\x1ftouch\x1f" + marker.string());
fs::remove_all(temp);
}

TEST(ExecutionBoundaryTest, OneWorkerRetainsRecipeStateAndCumulativeEffectsAcrossHooks) {
const auto temp = make_temp_dir("libxpkg-boundary-effects-");
const auto recipe = temp / "effects.lua";
Expand Down
Loading