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,
|
debug: uint,
|
||||||
disable: str,
|
disable: str,
|
||||||
drop_shell: bool,
|
drop_shell: bool,
|
||||||
|
// [ "KEY=VALUE" ]
|
||||||
|
env: []str,
|
||||||
// `--bunpen-help`
|
// `--bunpen-help`
|
||||||
help: bool,
|
help: bool,
|
||||||
home_paths: []str,
|
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(" omit `n` for light debugging, or specify n=0/1/2/3/4 where higher = more verbose")!;
|
||||||
fmt::println(" --bunpen-drop-shell")!;
|
fmt::println(" --bunpen-drop-shell")!;
|
||||||
fmt::println(" instead of running the program, drop into an interactive 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(" --bunpen-autodetect <existing|existingFile|existingFileOrParent|existingDirOrParent|existingOrParent|parent>")!;
|
||||||
fmt::println(" add files which appear later as CLI arguments into the sandbox")!;
|
fmt::println(" add files which appear later as CLI arguments into the sandbox")!;
|
||||||
fmt::println(" --bunpen-cap <all|sys_admin|net_raw|net_admin|...>")!;
|
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-debug=4" => parsed.debug = 4;
|
||||||
case "--bunpen-dns" => idx += 1; parsed.dns = expect_arg("--bunpen-dns", next)?;
|
case "--bunpen-dns" => idx += 1; parsed.dns = expect_arg("--bunpen-dns", next)?;
|
||||||
case "--bunpen-drop-shell" => parsed.drop_shell = true;
|
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-help" => parsed.help = true;
|
||||||
case "--bunpen-home-path" => idx += 1; append(parsed.home_paths, expect_arg("--bunpen-home-path", next)?);
|
case "--bunpen-home-path" => idx += 1; append(parsed.home_paths, expect_arg("--bunpen-home-path", next)?);
|
||||||
case "--bunpen-keep-ipc" => parsed.keep_ipc = true;
|
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,
|
// `args[0]` is, by convention, the name of the executable,
|
||||||
// relevant for multi-call binaries like `busybox`.
|
// relevant for multi-call binaries like `busybox`.
|
||||||
args: []str,
|
args: []str,
|
||||||
|
// *complete* environment with which to execute the binary
|
||||||
|
env: []str,
|
||||||
// path to the binary to be exec'd inside the sandbox.
|
// 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.
|
// 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,
|
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;
|
return params;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,7 +237,7 @@ fn try_as_path(
|
|||||||
|
|
||||||
// ensure that the specified path exists.
|
// ensure that the specified path exists.
|
||||||
// if it exists but not as a directory, succeed only if `dir_ok = true`.
|
// 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)
|
if (noexist_ok)
|
||||||
return void;
|
return void;
|
||||||
if (!file_ok && !dir_ok)
|
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));
|
log::printfln("[config/path/try] not adding path (exists, but as wrong type): {}", path::string(pathbuf));
|
||||||
return fs::wrongtype;
|
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
|
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=
|
tested=
|
||||||
rc=0
|
rc=0
|
||||||
|
@@ -13,14 +13,14 @@ use os;
|
|||||||
use os::exec;
|
use os::exec;
|
||||||
use types::c;
|
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...);
|
let joined = strings::join(" ", args...);
|
||||||
defer free(joined);
|
defer free(joined);
|
||||||
log::printfln("exec {} with argv: {}", path, 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
|
// XXX: os::exec::exec offers no way to preserve argv0, but it does
|
||||||
// work if you don't care about that:
|
// work if you don't care about that:
|
||||||
@@ -71,5 +71,5 @@ export fn main() void = {
|
|||||||
yield prepare_env(req);
|
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