Skip to content

Commit

Permalink
x86(wasi): support mapdir
Browse files Browse the repository at this point in the history
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
  • Loading branch information
ktock committed Aug 3, 2023
1 parent 66ab192 commit d3c291f
Show file tree
Hide file tree
Showing 12 changed files with 3,236 additions and 232 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ARG WASI_SDK_VERSION=19
ARG WASI_SDK_VERSION_FULL=${WASI_SDK_VERSION}.0
ARG WASI_VFS_VERSION=v0.3.0
ARG WIZER_VERSION=04e49c989542f2bf3a112d60fbf88a62cce2d0d0
ARG EMSDK_VERSION=3.1.42
ARG EMSDK_VERSION=3.1.40 # TODO: support recent version
ARG BINARYEN_VERSION=113
ARG BUSYBOX_VERSION=1_36_0
ARG RUNC_VERSION=v1.1.8
Expand Down
30 changes: 13 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ bin dev home lib32 libx32 mnt proc run srv tmp var
boot etc lib lib64 media opt root sbin sys usr
```

> NOTE: Directory mapping is available on non-x86_64 containers (TinyEMU-emulated ones) as of now. Other WASI features untested. Future version will support more WASI features.
Directories mapped to the WASM program is accessible on the container as well.

```
$ mkdir -p /tmp/share/ && echo hi > /tmp/share/from-host
$ wasmtime --mapdir /mnt/share::/tmp/share out.wasm cat /mnt/share/from-host
hi
```

### Container on Browser

Expand Down Expand Up @@ -184,8 +190,6 @@ $ wasmtime --mapdir /test/dir/share::/tmp/share /app/out.wasm ls /test/dir/share
hi
```

> NOTE: Directory mapping is available on non-x86_64 containers (TinyEMU-emulated) as of now. This should be available on all containers in the future.
## Motivation

Though more and more programming languages start to support WASM, it's not easy to run the existing programs on WASM.
Expand All @@ -201,7 +205,7 @@ The following shows the techniqual details:
- Builder: [BuildKit](https://github.com/moby/buildkit) runs the conversion steps written in Dockerfile.
- Emulator: [Bochs](https://bochs.sourceforge.io/) emulates x86_64 CPU on WASM. [TinyEMU](https://bellard.org/tinyemu/) emulates RISC-V CPU on WASM. They're compiled to WASM using [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) (for WASI and on-browser) and [emscripten](https://github.com/emscripten-core/emscripten) (for on-browser).
- Guest OS: Linux runs on the emulated CPU. [runc](https://github.com/opencontainers/runc) starts the container. Non-x86 and non-RISC-V containers runs with additional emulation by QEMU installed via [`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt).
- Directory Mapping: WASI filesystem API makes host directories visible to the emulator. TinyEMU mounts them to the guest linux via virtio-9p. Unsupported on Bochs (for x86_64 emulation) as of now.
- Directory Mapping: WASI filesystem API makes host directories visible to the emulator. Emulators mount them to the guest linux via virtio-9p.
- Packaging: [wasi-vfs](https://github.com/kateinoigakukun/wasi-vfs) (for WASI and on-browser) and emscripten (for on-browser) are used for packaging the dependencies. The kernel is pre-booted during the build using [wizer](https://github.com/bytecodealliance/wizer/) to minimize the startup latency (for WASI only as of now).
- Security: The converted container runs in the sandboxed WASM (WASI) VM with the limited access to the host system.

Expand All @@ -216,11 +220,11 @@ The following shows the techniqual details:

|runtime |stdio|mapdir|note|
|---|---|---|---|
|wasmtime|:heavy_check_mark:|:construction:||
|wamr(wasm-micro-runtime)|:heavy_check_mark:|:construction:||
|wazero|:heavy_check_mark:|:construction:||
|wasmer|:construction: (stdin unsupported)|:construction:|non-blocking stdin doesn't seem to work|
|wasmedge|:construction: (stdin unsupported)|:construction:|non-blocking stdin doesn't seem to work|
|wasmtime|:heavy_check_mark:|:heavy_check_mark:||
|wamr(wasm-micro-runtime)|:heavy_check_mark:|:heavy_check_mark:||
|wazero|:heavy_check_mark:|:heavy_check_mark:||
|wasmer|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work|
|wasmedge|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work|

### risc-v and other architecutre's containers

Expand All @@ -232,14 +236,6 @@ The following shows the techniqual details:
|wasmer|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work|
|wasmedge|:construction: (stdin unsupported)|:heavy_check_mark:|non-blocking stdin doesn't seem to work|

Example of mapdir with wasmtime:

```console
$ mkdir -p /tmp/share/ && echo hi > /tmp/share/hi
$ wasmtime --mapdir /test/dir/share::/tmp/share -- out.wasm --entrypoint=cat -- /test/dir/share/hi
hi
```

## Similar projects

There are several container runtimes support running WASM applications, but they don't run containers on WASM.
Expand Down
29 changes: 16 additions & 13 deletions cmd/init/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,25 @@ func doInit() error {
}

wg.Wait()
if infoSourceRemoteAddr == "" {

// WASI-related filesystems
for _, tag := range []string{rootFSTag, packFSTag} {
dst := filepath.Join("/mnt", tag)
if err := os.Mkdir(dst, 0777); err != nil {
return err
}
log.Printf("mounting %q to %q\n", tag, dst)
if err := syscall.Mount(tag, dst, "9p", 0, "trans=virtio,version=9p2000.L,msize=8192"); err != nil {
log.Printf("failed mounting %q: %v\n", tag, err)
break
}
}
initMounts := []string{rootFSTag, packFSTag}
if infoSourceRemoteAddr != "" {
initMounts = []string{rootFSTag}
}

// WASI-related filesystems
for _, tag := range initMounts {
dst := filepath.Join("/mnt", tag)
if err := os.Mkdir(dst, 0777); err != nil {
return err
}
log.Printf("mounting %q to %q\n", tag, dst)
if err := syscall.Mount(tag, dst, "9p", 0, "trans=virtio,version=9p2000.L,msize=8192"); err != nil {
log.Printf("failed mounting %q: %v\n", tag, err)
break
}
}

specD, err := os.ReadFile(cfg.Container.RuntimeConfigPath)
if err != nil {
return err
Expand Down
5 changes: 5 additions & 0 deletions patches/bochs/Bochs/bochs/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ NONINLINE_OBJS = \
plugin.o \
crc.o \
bxthread.o \
wasm.o \
@EXTRA_BX_OBJS@

EXTERN_ENVIRONMENT_OBJS = \
Expand Down Expand Up @@ -819,3 +820,7 @@ plugin.o: plugin.@CPP_SUFFIX@ bochs.h config.h osdep.h gui/paramtree.h logio.h \
plugin.h extplugin.h param_names.h pc_system.h bx_debug/debug.h config.h \
osdep.h memory/memory-bochs.h gui/siminterface.h gui/paramtree.h \
gui/gui.h plugin.h
wasm.o: wasm.@CPP_SUFFIX@ iodev/iodev.h bochs.h config.h osdep.h gui/paramtree.h \
logio.h misc/bswap.h plugin.h extplugin.h param_names.h pc_system.h \
bx_debug/debug.h config.h osdep.h memory/memory-bochs.h \
gui/siminterface.h gui/gui.h iodev/pci.h wasm.h
3 changes: 3 additions & 0 deletions patches/bochs/Bochs/bochs/iodev/devices.cc
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ void bx_devices_c::init(BX_MEM_C *newmem)
bulkIOQuantumsRequested = 0;
bulkIOQuantumsTransferred = 0;

#if defined(EMSCRIPTEN) || defined(WASI)
PLUG_load_plugin(mapdirVirtio9p, PLUGTYPE_STANDARD);
#endif
bx_init_plugins();

/* now perform checksum of CMOS memory */
Expand Down
119 changes: 7 additions & 112 deletions patches/bochs/Bochs/bochs/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
#include "iodev/usb/usb_common.h"
#endif

#if defined(EMSCRIPTEN) || defined(WASI)
#include "wasm.h"
#endif

#ifdef WASI
#include <wizer.h>
extern "C" {
Expand Down Expand Up @@ -315,34 +319,6 @@ void print_statistics_tree(bx_param_c *node, int level)

bool vm_init_done = false;

typedef struct {
char *contents;
int len;
int lim;
} FSVirtFile;

int write_info(FSVirtFile *f, int pos, int len, const char *str)
{
if ((pos + len) > f->lim) {
printf("too many write (%d > %d)", pos + len, f->lim);
return -1;
}
for (int i = 0; i < len; i++) {
f->contents[pos + i] = str[i];
}
return len;
}

int putchar_info(FSVirtFile *f, int pos, char c)
{
if ((pos + 1) > f->lim) {
printf("too many write (%d > %d)", pos + 1, f->lim);
return -1;
}
f->contents[pos] = c;
return 1;
}

int write_entrypoint(FSVirtFile *f, int pos1, const char *entrypoint)
{
int p, pos = pos1;
Expand Down Expand Up @@ -490,6 +466,9 @@ int init_wasi_info(int argc, char **argv, FSVirtFile *info)
}

info->len = pos;
#ifdef WASI
info->len += write_preopen_info(info, pos);
#endif
return 0;
}

Expand Down Expand Up @@ -735,90 +714,6 @@ extern "C" {
extern void __wasi_vfs_rt_init(void);
}

#include <wasi/libc.h>

// Populating preopen in the same way as wasi-libc does as well as updating
// the preopens list managed by wasi-libc.
// https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/libc-bottom-half/sources/preopens.c
// We use our list of preopenes for mounting them to the container.

typedef struct preopen {
/// The path prefix associated with the file descriptor.
const char *prefix;

/// The file descriptor.
__wasi_fd_t fd;
} preopen;

preopen *preopens;
size_t num_preopens = 0;
size_t preopen_capacity = 0;

static int populate_preopens() {
for (__wasi_fd_t fd = 3; fd != 0; ++fd) {
__wasi_prestat_t prestat;
__wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat);
if (ret == __WASI_ERRNO_BADF) {
break;
}
if (ret != __WASI_ERRNO_SUCCESS)
return -1;

switch (prestat.tag) {
case __WASI_PREOPENTYPE_DIR: {
char *prefix = (char *) malloc(prestat.u.dir.pr_name_len + 1);
if (prefix == NULL)
return -1;
if (__wasi_fd_prestat_dir_name(fd, (uint8_t *)prefix, prestat.u.dir.pr_name_len) != __WASI_ERRNO_SUCCESS)
return -1;
prefix[prestat.u.dir.pr_name_len] = '\0';

int off = 0;
while (1) {
if (prefix[off] == '/') {
off++;
} else if (prefix[off] == '.' && prefix[off + 1] == '/') {
off += 2;
} else if (prefix[off] == '.' && prefix[off + 1] == 0) {
off++;
} else {
break;
}
}

char *p = &(prefix[off]);
if (__wasilibc_register_preopened_fd(fd, strdup(p)) != 0)
return -1;

if (num_preopens == preopen_capacity) {
size_t new_capacity = preopen_capacity == 0 ? 4 : preopen_capacity * 2;
preopen *new_preopens = (preopen *)calloc(sizeof(preopen), new_capacity);
if (new_preopens == NULL) {
return -1;
}
memcpy(new_preopens, preopens, num_preopens * sizeof(preopen));
free(preopens);
preopens = new_preopens;
preopen_capacity = new_capacity;
}

char *prefix2 = strdup(p);
if (prefix2 == NULL)
return -1;

preopens[num_preopens++] = (preopen) { prefix2, fd, };
free(prefix);

break;
}
default:
break;
}
}

return 0;
}

int wasm_start(int (main)()) {
int result;
void *asyncify_buf;
Expand Down
1 change: 1 addition & 0 deletions patches/bochs/Bochs/bochs/plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ plugin_t bx_builtin_plugins[] = {
#if BX_SUPPORT_USB_XHCI
BUILTIN_OPTPCI_PLUGIN_ENTRY(usb_xhci),
#endif
BUILTIN_OPTPCI_PLUGIN_ENTRY(mapdirVirtio9p),
#if BX_SUPPORT_SOUNDLOW
BUILTIN_SND_PLUGIN_ENTRY(dummy),
BUILTIN_SND_PLUGIN_ENTRY(file),
Expand Down
3 changes: 3 additions & 0 deletions patches/bochs/Bochs/bochs/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ extern "C" {
#define BX_PLUGIN_HPET "hpet"
#define BX_PLUGIN_VOODOO "voodoo"

#define BX_PLUGIN_MAPDIR_VIRTIO_9P "mapdirVirtio9p"


#define BX_REGISTER_DEVICE_DEVMODEL(a,b,c,d) pluginRegisterDeviceDevmodel(a,b,c,d)
#define BX_UNREGISTER_DEVICE_DEVMODEL(a,b) pluginUnregisterDeviceDevmodel(a,b)
Expand Down Expand Up @@ -489,6 +491,7 @@ PLUGIN_ENTRY_FOR_IMG_MODULE(vbox);
PLUGIN_ENTRY_FOR_IMG_MODULE(vpc);
PLUGIN_ENTRY_FOR_IMG_MODULE(vvfat);

PLUGIN_ENTRY_FOR_MODULE(mapdirVirtio9p);
#endif

#ifdef __cplusplus
Expand Down
Loading

0 comments on commit d3c291f

Please sign in to comment.