bunpen: support --bunpen-env KEY=VALUE flag

this performs some variable expansion, and will be useful for e.g. `--bunpen-env 'MESA_SHADER_CACHE_DIR=$HOME/.cache/my-app/mesa_shader_cache_db'`
This commit is contained in:
2024-12-15 11:04:14 +00:00
parent 8141c94948
commit 60575640fd
4 changed files with 75 additions and 4 deletions

View File

@@ -13,6 +13,8 @@ export type cli_opts = struct {
debug: uint,
disable: str,
drop_shell: bool,
// [ "KEY=VALUE" ]
env: []str,
// `--bunpen-help`
help: bool,
home_paths: []str,
@@ -44,6 +46,12 @@ export fn usage() void = {
fmt::println(" omit `n` for light debugging, or specify n=0/1/2/3/4 where higher = more verbose")!;
fmt::println(" --bunpen-drop-shell")!;
fmt::println(" instead of running the program, drop into an interactive shell")!;
fmt::println(" --bunpen-env FOO=VALUE")!;
fmt::println(" set the environment variable FOO to VALUE within the launched program")!;
fmt::println(" certain shell-style substitutions are performed on the raw string ('FOO=VALUE'), including:")!;
fmt::println(" $HOME")!;
fmt::println(" $XDG_RUNTIME_DIR")!;
fmt::println(" $$ to escape a literal $")!;
fmt::println(" --bunpen-autodetect <existing|existingFile|existingFileOrParent|existingDirOrParent|existingOrParent|parent>")!;
fmt::println(" add files which appear later as CLI arguments into the sandbox")!;
fmt::println(" --bunpen-cap <all|sys_admin|net_raw|net_admin|...>")!;
@@ -155,6 +163,7 @@ fn parse_args_into(parsed: *cli_opts, args: []str) (void | errors::invalid) = {
case "--bunpen-debug=4" => parsed.debug = 4;
case "--bunpen-dns" => idx += 1; parsed.dns = expect_arg("--bunpen-dns", next)?;
case "--bunpen-drop-shell" => parsed.drop_shell = true;
case "--bunpen-env" => idx += 1; append(parsed.env, expect_arg("--bunpen-env", next)?);
case "--bunpen-help" => parsed.help = true;
case "--bunpen-home-path" => idx += 1; append(parsed.home_paths, expect_arg("--bunpen-home-path", next)?);
case "--bunpen-keep-ipc" => parsed.keep_ipc = true;

View File

@@ -27,6 +27,8 @@ export type exec_params = struct {
// `args[0]` is, by convention, the name of the executable,
// relevant for multi-call binaries like `busybox`.
args: []str,
// *complete* environment with which to execute the binary
env: []str,
// path to the binary to be exec'd inside the sandbox.
// if the user requested `--bunpen-drop-shell`, this will be their shell (e.g. /bin/sh), assuming `BUNPEN_DISABLE=1` wasn't specified.
bin: str,
@@ -57,6 +59,30 @@ fn cli_opts_get_exec_params(opts: cli_opts) exec_params = {
};
};
//---- ingest `env` ----//
let home = getenv_default("HOME");
let xdg_runtime_dir = getenv_default("XDG_RUNTIME_DIR");
for (let old .. os::getenvs()) {
let (old_key, _old_value) = strings::cut(old, "=");
let keep_old = true;
for (let new .. opts.env) {
let (new_key, _new_value) = strings::cut(new, "=");
if (old_key == new_key) {
keep_old = false;
};
};
if (keep_old) {
append(params.env, old);
};
};
for (let new .. opts.env) {
let substituted = strings::multireplace(new, ("$$", "$"), ("$HOME", home), ("$XDG_RUNTIME_DIR", xdg_runtime_dir));
append(params.env, substituted);
};
return params;
};
@@ -211,7 +237,7 @@ fn try_as_path(
// ensure that the specified path exists.
// if it exists but not as a directory, succeed only if `dir_ok = true`.
fn check_exists(pathbuf: *path::buffer, noexist_ok: bool, file_ok: bool, dir_ok: bool) (void | fs::error | path::error) = {
fn check_exists(pathbuf: *path::buffer, noexist_ok: bool, file_ok: bool, dir_ok: bool) (void | fs::error | path::error) = {
if (noexist_ok)
return void;
if (!file_ok && !dir_ok)
@@ -228,3 +254,12 @@ fn check_exists(pathbuf: *path::buffer, noexist_ok: bool, file_ok: bool, dir_ok:
log::printfln("[config/path/try] not adding path (exists, but as wrong type): {}", path::string(pathbuf));
return fs::wrongtype;
};
// return the value of the environment variable keyed by `key`, or "" if it
// doesn't exist
fn getenv_default(key: str) str = {
return match (os::getenv(key)) {
case void => yield "";
case let value: str => yield value;
};
};

View File

@@ -114,6 +114,33 @@ test_15_reap_children() {
ps x | grep -E 'Zs +[0-9]+:[0-9]+ \[true\] <defunct>' && return 1 || return 0
}
test_16_keep_env() {
ORIG_ENV=orig bunpen --bunpen-path / bash -c '[[ "$ORIG_ENV" = orig && -z "$NOT_ENV" ]]'
}
test_17_new_env() {
bunpen --bunpen-path / --bunpen-env NEW_ENV=new bash -c '[[ "$NEW_ENV" = new && -z "$NOT_ENV" ]]'
}
test_18_updated_env() {
UPD_ENV=orig bunpen --bunpen-path / --bunpen-env UPD_ENV=new bash -c '[[ "$UPD_ENV" = new && -z "$NOT_ENV" ]]'
}
test_19_substitute_env_home() {
# HOME defaults to /homeless-shelter
# XDG_RUNTIME_DIR defaults to (unset)
bunpen --bunpen-path / --bunpen-env 'H_ENV=/head$HOME/tail' --bunpen-env 'R_ENV=$XDG_RUNTIME_DIR/tail' bash -c \
'[[ "$H_ENV" = /head/homeless-shelter/tail && "$R_ENV" = /tail ]]'
}
test_20_substitute_env_all() {
XDG_RUNTIME_DIR=/r/t bunpen --bunpen-path / --bunpen-env 'H_ENV=/head$HOME/tail' --bunpen-env 'R_ENV=$XDG_RUNTIME_DIR/tail' bash -c \
'[[ "$H_ENV" = /head/homeless-shelter/tail && "$R_ENV" = /r/t/tail ]]'
}
test_21_substitute_env_escapes() {
bunpen --bunpen-path / --bunpen-env 'H_ENV=/head$$HOME/tail' --bunpen-env 'H_ENV2=$$$HOME/tail' bash -c \
'echo "$H_ENV2" && [[ "$H_ENV" = '"'"'/head$HOME/tail'"'"' && "$H_ENV2" = '"'"'$/homeless-shelter/tail'"'"' ]]'
}
tested=
rc=0

View File

@@ -13,14 +13,14 @@ use os;
use os::exec;
use types::c;
fn do_exec(path: str, args: []str) (os::exec::error | void) = {
fn do_exec(path: str, args: []str, env: []str) (os::exec::error | void) = {
{
let joined = strings::join(" ", args...);
defer free(joined);
log::printfln("exec {} with argv: {}", path, joined);
};
errors::ext::check("exec", rt::ext::execvpe(path, args, os::getenvs()));
errors::ext::check("exec", rt::ext::execvpe(path, args, env));
// XXX: os::exec::exec offers no way to preserve argv0, but it does
// work if you don't care about that:
@@ -71,5 +71,5 @@ export fn main() void = {
yield prepare_env(req);
};
errors::ext::check("exec <user command>", do_exec(exec_params.bin, exec_params.args));
errors::ext::check("exec <user command>", do_exec(exec_params.bin, exec_params.args, exec_params.env));
};