From b90592fb6ed2b920338b99eb77514374e0c3267c Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Sat, 2 Sep 2023 12:36:10 +0900 Subject: [PATCH] wasi: Support sharing envvar to container Signed-off-by: Kohei Tokunaga --- cmd/init/main.go | 2 ++ patches/bochs/Bochs/bochs/main.cc | 43 ++++++++++++++++++++++++++++++ patches/tinyemu/tinyemu/temu.c | 43 ++++++++++++++++++++++++++++++ patches/tinyemu/tinyemu/wasi.c | 2 ++ tests/integration/wamr_test.go | 15 +++++++++++ tests/integration/wasmedge_test.go | 15 +++++++++++ tests/integration/wasmer_test.go | 11 ++++++++ tests/integration/wasmtime_test.go | 11 ++++++++ tests/integration/wazero_test.go | 11 ++++++++ tests/wazero/main.go | 26 +++++++++++++++--- 10 files changed, 176 insertions(+), 3 deletions(-) diff --git a/cmd/init/main.go b/cmd/init/main.go index cd6aa15..21cf44f 100644 --- a/cmd/init/main.go +++ b/cmd/init/main.go @@ -273,6 +273,8 @@ func patchSpec(s runtimespec.Spec, infoD []byte, imageConfig imagespec.Image) ru args = append(args, strings.ReplaceAll(string(o[prev:]), "\\ ", " ")) case "e": entrypoint = []string{o} + case "env": + s.Process.Env = append(s.Process.Env, o) default: log.Printf("unsupported prefix: %q", inst) } diff --git a/patches/bochs/Bochs/bochs/main.cc b/patches/bochs/Bochs/bochs/main.cc index 20bd13c..1869575 100644 --- a/patches/bochs/Bochs/bochs/main.cc +++ b/patches/bochs/Bochs/bochs/main.cc @@ -40,6 +40,7 @@ #ifdef WASI #include +#include extern "C" { #include "jmp.h" } @@ -382,6 +383,34 @@ int write_args(FSVirtFile *f, int argc, char **argv, int optind, int pos1) return pos - pos1; } +int write_env(FSVirtFile *f, int pos1, const char *env) +{ + int p, pos = pos1; + + p = write_info(f, pos, 5, "env: "); + if (p < 0) { + return -1; + } + pos += p; + for (int j = 0; j < strlen(env); j++) { + if (env[j] == '\n') { + p = write_info(f, pos, 2, "\\\n"); + if (p != 2) { + return -1; + } + pos += p; + continue; + } + if (putchar_info(f, pos++, env[j]) != 1) { + return -1; + } + } + if (putchar_info(f, pos++, '\n') != 1) { + return -1; + } + return pos - pos1; +} + static struct option options[] = { { "help", no_argument, NULL, 'h' }, { "no-stdin", no_argument }, @@ -465,6 +494,19 @@ int init_wasi_info(int argc, char **argv, FSVirtFile *info) pos += p; } +#ifdef WASI + // TODO: support emscripten; it seems some default variables are passed, which shouldn't be inherited by the container. + // https://github.com/emscripten-core/emscripten/blob/0566a76b500bd2bbd535e108f657fce1db7f6f75/src/library_wasi.js#L62 + for (char **env = environ; *env; ++env) { + int p = write_env(info, pos, *env); + if (p < 0) { + printf("failed to prepare env info\n"); + exit(1); + } + pos += p; + } +#endif + info->len = pos; #ifdef WASI info->len += write_preopen_info(info, pos); @@ -752,6 +794,7 @@ int CDECL start_vm(void) int CDECL main(int argc, char *argv[]) { #ifdef WASI + __wasilibc_ensure_environ(); __wasi_vfs_rt_init(); if (populate_preopens() != 0) { // register wasi-vfs dir to wasi-libc and our list fprintf(stderr, "failed to populate preopens"); diff --git a/patches/tinyemu/tinyemu/temu.c b/patches/tinyemu/tinyemu/temu.c index 0cbc29e..57d966b 100644 --- a/patches/tinyemu/tinyemu/temu.c +++ b/patches/tinyemu/tinyemu/temu.c @@ -58,6 +58,8 @@ #include "wasi.h" #endif +extern char **environ; + #ifdef ON_BROWSER #include #endif @@ -918,6 +920,34 @@ int write_args(FSVirtFile *f, int argc, char **argv, int optind, int pos1) return pos - pos1; } +int write_env(FSVirtFile *f, int pos1, const char *env) +{ + int p, pos = pos1; + + p = write_info(f, pos, 5, "env: "); + if (p < 0) { + return -1; + } + pos += p; + for (int j = 0; j < strlen(env); j++) { + if (env[j] == '\n') { + p = write_info(f, pos, 2, "\\\n"); + if (p != 2) { + return -1; + } + pos += p; + continue; + } + if (putchar_info(f, pos++, env[j]) != 1) { + return -1; + } + } + if (putchar_info(f, pos++, '\n') != 1) { + return -1; + } + return pos - pos1; +} + int main(int argc, char **argv) { #ifdef WASI @@ -984,6 +1014,19 @@ int main(int argc, char **argv) pos += p; } +#ifdef WASI + // TODO: support emscripten; it seems some default variables are passed, which shouldn't be inherited by the container. + // https://github.com/emscripten-core/emscripten/blob/0566a76b500bd2bbd535e108f657fce1db7f6f75/src/library_wasi.js#L62 + for (char **env = environ; *env; ++env) { + int p = write_env(info, pos, *env); + if (p < 0) { + printf("failed to prepare env info\n"); + exit(1); + } + pos += p; + } +#endif + info->len = pos; #ifdef WASI info->len += write_preopen_info(info, pos); diff --git a/patches/tinyemu/tinyemu/wasi.c b/patches/tinyemu/tinyemu/wasi.c index 356c111..b601021 100644 --- a/patches/tinyemu/tinyemu/wasi.c +++ b/patches/tinyemu/tinyemu/wasi.c @@ -26,6 +26,7 @@ #include #include +#include #include "cutils.h" #include "fs.h" @@ -160,6 +161,7 @@ extern void __wasi_vfs_rt_init(void); int init_wasi() { + __wasilibc_ensure_environ(); __wasi_vfs_rt_init(); // initialize wasi-vfs if (populate_preopens() != 0) { // register mapdir and wasi-vfs dir to wasi-libc and our list fprintf(stderr, "failed to populate preopens"); diff --git a/tests/integration/wamr_test.go b/tests/integration/wamr_test.go index 7c8f974..3426921 100644 --- a/tests/integration/wamr_test.go +++ b/tests/integration/wamr_test.go @@ -127,5 +127,20 @@ func TestWamr(t *testing.T) { }, ), }, + { + Name: "wamr-env", + Runtime: "iwasm", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + ImageName: "test2.wasm", + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, exec.Command("wamrc", "-o", filepath.Join(workdir, "test2.wasm"), filepath.Join(workdir, "test.wasm")).Run()) + }, + RuntimeOpts: utils.StringFlags("--env=AAA=hello", "--env=BBB=world"), + Args: utils.StringFlags("/bin/sh", "-c", "echo -n $AAA $BBB"), + Want: utils.WantString("hello world"), + }, }...) } diff --git a/tests/integration/wasmedge_test.go b/tests/integration/wasmedge_test.go index 320d07e..5a3a23a 100644 --- a/tests/integration/wasmedge_test.go +++ b/tests/integration/wasmedge_test.go @@ -55,5 +55,20 @@ func TestWasmedge(t *testing.T) { Args: utils.StringFlags("--no-stdin", "cat", "/map/dir/hi"), Want: utils.WantString("teststring"), }, + { + Name: "wasmedge-env", + Runtime: "wasmedge", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + ImageName: "test2.wasm", + Prepare: func(t *testing.T, workdir string) { + assert.NilError(t, exec.Command("wasmedgec", filepath.Join(workdir, "test.wasm"), filepath.Join(workdir, "test2.wasm")).Run()) + }, + RuntimeOpts: utils.StringFlags("--env=AAA=hello", "--env=BBB=world"), + Args: utils.StringFlags("--no-stdin", "/bin/sh", "-c", "echo -n $AAA $BBB"), // NOTE: stdin unsupported on wasmedge as of now + Want: utils.WantString("hello world"), + }, }...) } diff --git a/tests/integration/wasmer_test.go b/tests/integration/wasmer_test.go index 08c0981..2a2a4d3 100644 --- a/tests/integration/wasmer_test.go +++ b/tests/integration/wasmer_test.go @@ -47,5 +47,16 @@ func TestWasmer(t *testing.T) { Args: utils.StringFlags("--", "--no-stdin", "cat", "/mapped/dir/test/hi"), Want: utils.WantString("teststring"), }, + { + Name: "wasmer-env", + Runtime: "wasmer", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + RuntimeOpts: utils.StringFlags("--env=AAA=hello", "--env=BBB=world"), + Args: utils.StringFlags("--", "--no-stdin", "/bin/sh", "-c", "echo -n $AAA $BBB"), // wasmer requires "--" before flags we pass to the wasm program. + Want: utils.WantString("hello world"), + }, }...) } diff --git a/tests/integration/wasmtime_test.go b/tests/integration/wasmtime_test.go index 063f016..d1251d6 100644 --- a/tests/integration/wasmtime_test.go +++ b/tests/integration/wasmtime_test.go @@ -104,6 +104,17 @@ func TestWasmtime(t *testing.T) { [2]string{"echo -n hello > /mapped/dir/test/from-guest/testhello\n", ""}, ), }, + { + Name: "wasmtime-env", + Runtime: "wasmtime", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + RuntimeOpts: utils.StringFlags("--env=AAA=hello", "--env=BBB=world"), + Args: utils.StringFlags("/bin/sh", "-c", "echo -n $AAA $BBB"), + Want: utils.WantString("hello world"), + }, // Other architectures { Name: "wasmtime-hello-arch-aarch64", diff --git a/tests/integration/wazero_test.go b/tests/integration/wazero_test.go index 64a4a5a..3e7693a 100644 --- a/tests/integration/wazero_test.go +++ b/tests/integration/wazero_test.go @@ -106,5 +106,16 @@ func TestWazero(t *testing.T) { [2]string{"echo -n hello > /mapdir/from-guest/testhello\n", ""}, ), }, + { + Name: "wazero-env", + Runtime: "wazero-test", + Inputs: []utils.Input{ + {Image: "alpine:3.17", Architecture: utils.X86_64}, + {Image: "riscv64/alpine:20221110", ConvertOpts: []string{"--target-arch=riscv64"}, Architecture: utils.RISCV64}, + }, + RuntimeOpts: utils.StringFlags("--env=AAA=hello", "--env=BBB=world"), + Args: utils.StringFlags("/bin/sh", "-c", "echo -n $AAA $BBB"), + Want: utils.WantString("hello world"), + }, }...) } diff --git a/tests/wazero/main.go b/tests/wazero/main.go index 008a6bd..360cf54 100644 --- a/tests/wazero/main.go +++ b/tests/wazero/main.go @@ -4,6 +4,7 @@ import ( "context" crand "crypto/rand" "flag" + "fmt" "os" "strings" @@ -15,6 +16,8 @@ func main() { var ( mapDir = flag.String("mapdir", "", "directory mapping to the image") ) + var envs envFlags + flag.Var(&envs, "env", "environment variables") flag.Parse() args := flag.Args() @@ -41,11 +44,28 @@ func main() { if err != nil { panic(err) } - // we forcibly enable non-blocking read of stdin. - _, err = r.InstantiateModule(ctx, compiled, - wazero.NewModuleConfig().WithSysWalltime().WithSysNanotime().WithSysNanosleep().WithRandSource(crand.Reader).WithStdout(os.Stdout).WithStderr(os.Stderr).WithStdin(os.Stdin).WithFSConfig(fsConfig).WithArgs(append([]string{"arg0"}, args[1:]...)...)) + conf := wazero.NewModuleConfig().WithSysWalltime().WithSysNanotime().WithSysNanosleep().WithRandSource(crand.Reader).WithStdout(os.Stdout).WithStderr(os.Stderr).WithStdin(os.Stdin).WithFSConfig(fsConfig).WithArgs(append([]string{"arg0"}, args[1:]...)...) + for _, v := range envs { + es := strings.SplitN(v, "=", 2) + if len(es) == 2 { + conf = conf.WithEnv(es[0], es[1]) + } else { + panic("env must be a key value pair") + } + } + _, err = r.InstantiateModule(ctx, compiled, conf) if err != nil { panic(err) } return } + +type envFlags []string + +func (i *envFlags) String() string { + return fmt.Sprintf("%v", []string(*i)) +} +func (i *envFlags) Set(value string) error { + *i = append(*i, value) + return nil +}