package os_old import "core:sys/wasm/wasi" import "base:runtime" Handle :: distinct i32 _Platform_Error :: wasi.errno_t INVALID_HANDLE :: -1 O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 O_RDWR :: 0x00002 O_CREATE :: 0x00040 O_EXCL :: 0x00080 O_NOCTTY :: 0x00100 O_TRUNC :: 0x00200 O_NONBLOCK :: 0x00800 O_APPEND :: 0x00400 O_SYNC :: 0x01000 O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 args := _alloc_command_line_arguments() @(private, require_results) _alloc_command_line_arguments :: proc "contextless" () -> []string { context = runtime.default_context() cmd_args := make([]string, len(runtime.args__)) for &arg, i in cmd_args { arg = string(runtime.args__[i]) } return cmd_args } @(private, fini) _delete_command_line_arguments :: proc "contextless" () { context = runtime.default_context() delete(args) } // WASI works with "preopened" directories, the environment retrieves directories // (for example with `wasmtime --dir=. module.wasm`) and those given directories // are the only ones accessible by the application. // // So in order to facilitate the `os` API (absolute paths etc.) we keep a list // of the given directories and match them when needed (notably `os.open`). @(private) Preopen :: struct { fd: wasi.fd_t, prefix: string, } @(private) preopens: []Preopen @(init, private) init_preopens :: proc "contextless" () { strip_prefixes :: proc "contextless"(path: string) -> string { path := path loop: for len(path) > 0 { switch { case path[0] == '/': path = path[1:] case len(path) > 2 && path[0] == '.' && path[1] == '/': path = path[2:] case len(path) == 1 && path[0] == '.': path = path[1:] case: break loop } } return path } context = runtime.default_context() dyn_preopens: [dynamic]Preopen loop: for fd := wasi.fd_t(3); ; fd += 1 { desc, err := wasi.fd_prestat_get(fd) #partial switch err { case .BADF: break loop case: panic("fd_prestat_get returned an unexpected error") case .SUCCESS: } switch desc.tag { case .DIR: buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens") if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { panic("could not get filesystem preopen dir name") } append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))}) } } preopens = dyn_preopens[:] } @(require_results) wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { @(require_results) prefix_matches :: proc(prefix, path: string) -> bool { // Empty is valid for any relative path. if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { return true } if len(path) < len(prefix) { return false } if path[:len(prefix)] != prefix { return false } // Only match on full components. i := len(prefix) for i > 0 && prefix[i-1] == '/' { i -= 1 } return path[i] == '/' } path := path for len(path) > 0 && path[0] == '/' { path = path[1:] } match: Preopen #reverse for preopen in preopens { if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { match = preopen } } if match.fd == 0 { return 0, "", false } relative := path[len(match.prefix):] for len(relative) > 0 && relative[0] == '/' { relative = relative[1:] } if len(relative) == 0 { relative = "." } return match.fd, relative, true } write :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) return int(n), Platform_Error(err) } read :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.iovec_t(data) n, err := wasi.fd_read(wasi.fd_t(fd), {iovs}) return int(n), Platform_Error(err) } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) return int(n), Platform_Error(err) } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { iovs := wasi.iovec_t(data) n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) return int(n), Platform_Error(err) } @(require_results) open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { oflags: wasi.oflags_t if mode & O_CREATE == O_CREATE { oflags += {.CREATE} } if mode & O_EXCL == O_EXCL { oflags += {.EXCL} } if mode & O_TRUNC == O_TRUNC { oflags += {.TRUNC} } rights: wasi.rights_t = {.FD_SEEK, .FD_FILESTAT_GET} switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { case O_RDONLY: rights += {.FD_READ} case O_WRONLY: rights += {.FD_WRITE} case O_RDWR: rights += {.FD_READ, .FD_WRITE} } fdflags: wasi.fdflags_t if mode & O_APPEND == O_APPEND { fdflags += {.APPEND} } if mode & O_NONBLOCK == O_NONBLOCK { fdflags += {.NONBLOCK} } if mode & O_SYNC == O_SYNC { fdflags += {.SYNC} } dir_fd, relative, ok := wasi_match_preopen(path) if !ok { return INVALID_HANDLE, Errno(wasi.errno_t.BADF) } fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) return Handle(fd), Platform_Error(err) } close :: proc(fd: Handle) -> Errno { err := wasi.fd_close(wasi.fd_t(fd)) return Platform_Error(err) } flush :: proc(fd: Handle) -> Error { // do nothing return nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence)) return i64(n), Platform_Error(err) } @(require_results) current_thread_id :: proc "contextless" () -> int { return 0 } @(private, require_results) _processor_core_count :: proc() -> int { return 1 } @(require_results) file_size :: proc(fd: Handle) -> (size: i64, err: Errno) { stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return size = i64(stat.size) return } exit :: proc "contextless" (code: int) -> ! { runtime._cleanup_runtime_contextless() wasi.proc_exit(wasi.exitcode_t(code)) } @(require_results) lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { return "", false } @(require_results) lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { return "", .Env_Var_Not_Found } lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} @(require_results) get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } @(require_results) get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { value, _ = lookup_env(buf, key) return } get_env :: proc{get_env_alloc, get_env_buf}