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:
@@ -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;
|
||||
|
@@ -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;
|
||||
};
|
||||
};
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
};
|
||||
|
Reference in New Issue
Block a user