diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index ef578f5c0..8266f8ffc 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -5,6 +5,7 @@ #+build !netbsd #+build !darwin #+build !js +#+build !wasi package crypto HAS_RAND_BYTES :: false diff --git a/core/crypto/rand_wasi.odin b/core/crypto/rand_wasi.odin new file mode 100644 index 000000000..9653fb985 --- /dev/null +++ b/core/crypto/rand_wasi.odin @@ -0,0 +1,13 @@ +package crypto + +import "core:fmt" +import "core:sys/wasm/wasi" + +HAS_RAND_BYTES :: true + +@(private) +_rand_bytes :: proc(dst: []byte) { + if err := wasi.random_get(dst); err != nil { + fmt.panicf("crypto: wasi.random_get failed: %v", err) + } +} diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 14fddde50..36cac2597 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -39,8 +39,11 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info } n := len(fimpl.name)+1 - non_zero_resize(&it.impl.fullpath, n+len(sname)) - n += copy(it.impl.fullpath[n:], sname) + if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil { + // Can't really tell caller we had an error, sad. + return + } + copy(it.impl.fullpath[n:], sname) fi = internal_stat(stat, string(it.impl.fullpath[:])) ok = true @@ -60,7 +63,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera iter.f = f iter.impl.idx = 0 - iter.impl.fullpath.allocator = file_allocator() + iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return append(&iter.impl.fullpath, impl.name) append(&iter.impl.fullpath, "/") defer if err != nil { delete(iter.impl.fullpath) } diff --git a/core/os/os2/dir_wasi.odin b/core/os/os2/dir_wasi.odin new file mode 100644 index 000000000..e4349069a --- /dev/null +++ b/core/os/os2/dir_wasi.odin @@ -0,0 +1,110 @@ +#+private +package os2 + +import "base:intrinsics" +import "core:sys/wasm/wasi" + +Read_Directory_Iterator_Impl :: struct { + fullpath: [dynamic]byte, + buf: []byte, + off: int, + idx: int, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + fimpl := (^File_Impl)(it.f.impl) + + buf := it.impl.buf[it.impl.off:] + + index = it.impl.idx + it.impl.idx += 1 + + for { + if len(buf) < size_of(wasi.dirent_t) { + return + } + + entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf))) + buf = buf[size_of(wasi.dirent_t):] + + if len(buf) < int(entry.d_namlen) { + // shouldn't be possible. + return + } + + name := string(buf[:entry.d_namlen]) + buf = buf[entry.d_namlen:] + it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen) + + if name == "." || name == ".." { + continue + } + + n := len(fimpl.name)+1 + if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil { + // Can't really tell caller we had an error, sad. + return + } + copy(it.impl.fullpath[n:], name) + + stat, err := wasi.path_filestat_get(__fd(it.f), {}, name) + if err != nil { + // Can't stat, fill what we have from dirent. + stat = { + ino = entry.d_ino, + filetype = entry.d_type, + } + } + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + iter.f = f + + buf: [dynamic]byte + buf.allocator = file_allocator() + defer if err != nil { delete(buf) } + + // NOTE: this is very grug. + for { + non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2) or_return + + n, _err := wasi.fd_readdir(__fd(f), buf[:], 0) + if _err != nil { + err = _get_platform_error(_err) + return + } + + if n < len(buf) { + non_zero_resize(&buf, n) + break + } + + assert(n == len(buf)) + } + iter.impl.buf = buf[:] + + iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return + append(&iter.impl.fullpath, impl.name) + append(&iter.impl.fullpath, "/") + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + delete(it.impl.buf, file_allocator()) + delete(it.impl.fullpath) + it^ = {} +} diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin new file mode 100644 index 000000000..8bf4eff38 --- /dev/null +++ b/core/os/os2/env_wasi.odin @@ -0,0 +1,186 @@ +#+private +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sync" +import "core:sys/wasm/wasi" + +g_env: map[string]string +g_env_buf: []byte +g_env_mutex: sync.RW_Mutex +g_env_error: Error +g_env_built: bool + +build_env :: proc() -> (err: Error) { + if g_env_built || g_env_error != nil { + return g_env_error + } + + sync.guard(&g_env_mutex) + + if g_env_built || g_env_error != nil { + return g_env_error + } + + defer if err != nil { + g_env_error = err + } + + num_envs, size_of_envs, _err := wasi.environ_sizes_get() + if _err != nil { + return _get_platform_error(_err) + } + + g_env = make(map[string]string, num_envs, file_allocator()) or_return + defer if err != nil { delete(g_env) } + + g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return + defer if err != nil { delete(g_env_buf, file_allocator()) } + + TEMP_ALLOCATOR_GUARD() + + envs := make([]cstring, num_envs, temp_allocator()) or_return + + _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) + if _err != nil { + return _get_platform_error(_err) + } + + for env in envs { + key, _, value := strings.partition(string(env), "=") + g_env[key] = value + } + + g_env_built = true + return +} + +delete_string_if_not_original :: proc(str: string) { + start := uintptr(raw_data(g_env_buf)) + end := start + uintptr(len(g_env_buf)) + ptr := uintptr(raw_data(str)) + if ptr < start || ptr > end { + delete(str, file_allocator()) + } +} + +@(require_results) +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if err := build_env(); err != nil { + return + } + + sync.shared_guard(&g_env_mutex) + + value = g_env[key] or_return + value, _ = clone_string(value, allocator) + return +} + +@(require_results) +_set_env :: proc(key, value: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + key_ptr, value_ptr, just_inserted, err := map_entry(&g_env, key) + if err != nil { + return false + } + + alloc_err: runtime.Allocator_Error + + if just_inserted { + key_ptr^, alloc_err = clone_string(key, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + return false + } + + value_ptr^, alloc_err = clone_string(value, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + delete(key_ptr^, file_allocator()) + return false + } + + return true + } + + delete_string_if_not_original(value_ptr^) + + value_ptr^, alloc_err = clone_string(value, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + return false + } + + return true +} + +@(require_results) +_unset_env :: proc(key: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + dkey, dval := delete_key(&g_env, key) + delete_string_if_not_original(dkey) + delete_string_if_not_original(dval) + return true +} + +_clear_env :: proc() { + sync.guard(&g_env_mutex) + + for k, v in g_env { + delete_string_if_not_original(k) + delete_string_if_not_original(v) + } + + delete(g_env_buf, file_allocator()) + g_env_buf = {} + + clear(&g_env) + + g_env_built = true +} + +@(require_results) +_environ :: proc(allocator: runtime.Allocator) -> []string { + if err := build_env(); err != nil { + return nil + } + + sync.shared_guard(&g_env_mutex) + + envs, alloc_err := make([]string, len(g_env), allocator) + if alloc_err != nil { + return nil + } + + defer if alloc_err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + + i: int + for k, v in g_env { + defer i += 1 + + envs[i], alloc_err = concatenate({k, "=", v}, allocator) + if alloc_err != nil { + return nil + } + } + + return envs +} diff --git a/core/os/os2/errors_wasi.odin b/core/os/os2/errors_wasi.odin new file mode 100644 index 000000000..b88e5b81e --- /dev/null +++ b/core/os/os2/errors_wasi.odin @@ -0,0 +1,47 @@ +#+private +package os2 + +import "base:runtime" + +import "core:slice" +import "core:sys/wasm/wasi" + +_Platform_Error :: wasi.errno_t + +_error_string :: proc(errno: i32) -> string { + e := wasi.errno_t(errno) + if e == .NONE { + return "" + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" +} + +_get_platform_error :: proc(errno: wasi.errno_t) -> Error { + #partial switch errno { + case .PERM: + return .Permission_Denied + case .EXIST: + return .Exist + case .NOENT: + return .Not_Exist + case .TIMEDOUT: + return .Timeout + case .PIPE: + return .Broken_Pipe + case .BADF: + return .Invalid_File + case .NOMEM: + return .Out_Of_Memory + case .NOSYS: + return .Unsupported + case: + return Platform_Error(errno) + } +} diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin new file mode 100644 index 000000000..2b722e5dd --- /dev/null +++ b/core/os/os2/file_wasi.odin @@ -0,0 +1,534 @@ +#+private +package os2 + +import "base:runtime" + +import "core:io" +import "core:sys/wasm/wasi" +import "core:time" + +// NOTE: Don't know if there is a max in wasi. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + fd: wasi.fd_t, + allocator: runtime.Allocator, +} + +// 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`). +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +preopens: []Preopen + +@(init) +init_std_files :: proc() { + new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { + impl.file.impl = impl + impl.allocator = runtime.nil_allocator() + impl.fd = fd + impl.name = string(name) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], 0, "/dev/stdin") + stdout = new_std(&files[1], 1, "/dev/stdout") + stderr = new_std(&files[2], 2, "/dev/stderr") +} + +@(init) +init_preopens :: proc() { + strip_prefixes :: proc(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 + } + + n: int + n_loop: for fd := wasi.fd_t(3); ; fd += 1 { + _, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break n_loop + case .SUCCESS: n += 1 + case: + print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get") + break n_loop + } + } + + alloc_err: runtime.Allocator_Error + preopens, alloc_err = make([]Preopen, n, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopens") + return + } + + loop: for &preopen, i in preopens { + fd := wasi.fd_t(3 + i) + + desc, err := wasi.fd_prestat_get(fd) + assert(err == .SUCCESS) + + switch desc.tag { + case .DIR: + buf: []byte + buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name") + continue loop + } + + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name") + continue loop + } + + preopen.fd = fd + preopen.prefix = strip_prefixes(string(buf)) + } + } +} + +@(require_results) +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 + if path == "" { + return 0, "", false + } + + 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 +} + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return nil, .Invalid_Path + } + + oflags: wasi.oflags_t + if .Create in flags { oflags += {.CREATE} } + if .Excl in flags { oflags += {.EXCL} } + if .Trunc in flags { oflags += {.TRUNC} } + + fdflags: wasi.fdflags_t + if .Append in flags { fdflags += {.APPEND} } + if .Sync in flags { fdflags += {.SYNC} } + + // NOTE: rights are adjusted to what this package's functions might want to call. + rights: wasi.rights_t + if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} } + if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} } + + fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + if fderr != nil { + err = _get_platform_error(fderr) + return + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + impl := new(File_Impl, allocator) or_return + defer if err != nil { free(impl, allocator) } + + impl.allocator = allocator + // NOTE: wasi doesn't really do full paths afact. + impl.name = clone_string(name, allocator) or_return + impl.fd = wasi.fd_t(handle) + impl.file.impl = impl + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + + return &impl.file, nil +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if errno := wasi.fd_close(f.fd); errno != nil { + err = _get_platform_error(errno) + } + + delete(f.name, f.allocator) + free(f, f.allocator) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> wasi.fd_t { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + return _get_platform_error(wasi.fd_sync(__fd(f))) +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size))) +} + +_remove :: proc(name: string) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + err := wasi.path_remove_directory(dir_fd, relative) + if err == .NOTDIR { + err = wasi.path_unlink_file(dir_fd, relative) + } + + return _get_platform_error(err) +} + +_rename :: proc(old_path, new_path: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_path) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_path) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative)) +} + +_link :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + if src_dir_fd != new_dir_fd { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative)) +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return "", .Invalid_Path + } + + n, _err := wasi.path_readlink(dir_fd, relative, nil) + if _err != nil { + err = _get_platform_error(_err) + return + } + + buf := make([]byte, n, allocator) or_return + + _, _err = wasi.path_readlink(dir_fd, relative, buf) + s = string(buf) + err = _get_platform_error(_err) + return +} + +_chdir :: proc(name: string) -> Error { + return .Unsupported +} + +_fchdir :: proc(f: ^File) -> Error { + return .Unsupported +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + return .Unsupported +} + +_chmod :: proc(name: string, mode: int) -> Error { + return .Unsupported +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM})) +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM})) +} + +_exists :: proc(path: string) -> bool { + dir_fd, relative, ok := match_preopen(path) + if !ok { + return false + } + + _, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if err != nil { + return false + } + + return true +} + +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) + fd := f.fd + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_read(fd, {p[:to_read]}) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset)) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Write: + p := p + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_write(fd, {p[:to_write]}) + if _err != nil { + err = .Unknown + return + } + p = p[_n:] + n += i64(_n) + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset)) + if _err != nil { + err = .Unknown + return + } + + p = p[_n:] + n += i64(_n) + offset += i64(_n) + } + return + + case .Seek: + #assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start)) + #assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current)) + #assert(int(wasi.whence_t.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + _n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence)) + #partial switch _err { + case .INVAL: + err = .Invalid_Offset + case: + err = .Unknown + case .SUCCESS: + n = i64(_n) + } + return + + case .Size: + stat, _err := wasi.fd_filestat_get(fd) + if _err != nil { + err = .Unknown + return + } + + n = i64(stat.size) + return + + case .Flush: + ferr := _sync(&f.file) + err = error_to_io_error(ferr) + return + + case .Close, .Destroy: + ferr := _close(f) + err = error_to_io_error(ferr) + return + + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + + case: + return 0, .Empty + } +} diff --git a/core/os/os2/heap_wasi.odin b/core/os/os2/heap_wasi.odin new file mode 100644 index 000000000..7da3c4845 --- /dev/null +++ b/core/os/os2/heap_wasi.odin @@ -0,0 +1,6 @@ +#+private +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 5ffdac28e..e6b95c0d4 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -81,7 +81,7 @@ _remove_all :: proc(path: string) -> Error { fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) if entry.d_type == .DIR { - _remove_all(fullpath[:len(fullpath)-1]) + _remove_all(fullpath[:len(fullpath)-1]) or_return } else { if posix.unlink(cstring(raw_data(fullpath))) != .OK { return _get_platform_error() diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin new file mode 100644 index 000000000..17efbff62 --- /dev/null +++ b/core/os/os2/path_wasi.odin @@ -0,0 +1,84 @@ +#+private +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/wasm/wasi" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_create_directory(dir_fd, relative)) +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + TEMP_ALLOCATOR_GUARD() + + if exists(path) { + return .Exist + } + + clean_path := filepath.clean(path, temp_allocator()) + return internal_mkdir_all(clean_path) + + internal_mkdir_all :: proc(path: string) -> Error { + dir, file := filepath.split(path) + if file != path && dir != "/" { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir) or_return + } + + err := _mkdir(path, 0) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> (err: Error) { + // PERF: this works, but wastes a bunch of memory using the read_directory_iterator API + // and using open instead of wasi fds directly. + { + dir := open(path) or_return + defer close(dir) + + iter := read_directory_iterator_create(dir) or_return + defer read_directory_iterator_destroy(&iter) + + for fi in read_directory_iterator(&iter) { + if fi.type == .Directory { + _remove_all(fi.fullpath) or_return + } else { + remove(fi.fullpath) or_return + } + } + } + + return remove(path) +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return ".", .Unsupported +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/pipe_wasi.odin b/core/os/os2/pipe_wasi.odin new file mode 100644 index 000000000..19c11b51d --- /dev/null +++ b/core/os/os2/pipe_wasi.odin @@ -0,0 +1,13 @@ +#+private +package os2 + +_pipe :: proc() -> (r, w: ^File, err: Error) { + err = .Unsupported + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/process_wasi.odin b/core/os/os2/process_wasi.odin new file mode 100644 index 000000000..6ebfe3788 --- /dev/null +++ b/core/os/os2/process_wasi.odin @@ -0,0 +1,89 @@ +#+private +package os2 + +import "base:runtime" + +import "core:time" +import "core:sys/wasm/wasi" + +_exit :: proc "contextless" (code: int) -> ! { + wasi.proc_exit(wasi.exitcode_t(code)) +} + +_get_uid :: proc() -> int { + return 0 +} + +_get_euid :: proc() -> int { + return 0 +} + +_get_gid :: proc() -> int { + return 0 +} + +_get_egid :: proc() -> int { + return 0 +} + +_get_pid :: proc() -> int { + return 0 +} + +_get_ppid :: proc() -> int { + return 0 +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + err = .Unsupported + return +} + +_process_close :: proc(process: Process) -> Error { + return .Unsupported +} + +_process_kill :: proc(process: Process) -> (err: Error) { + return .Unsupported +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/os/os2/stat_wasi.odin b/core/os/os2/stat_wasi.odin new file mode 100644 index 000000000..2992c6267 --- /dev/null +++ b/core/os/os2/stat_wasi.odin @@ -0,0 +1,101 @@ +#+private +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/wasm/wasi" +import "core:time" + +internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + fi.name = filepath.base(fi.fullpath) + + fi.inode = u128(stat.ino) + fi.size = i64(stat.size) + + switch stat.filetype { + case .BLOCK_DEVICE: fi.type = .Block_Device + case .CHARACTER_DEVICE: fi.type = .Character_Device + case .DIRECTORY: fi.type = .Directory + case .REGULAR_FILE: fi.type = .Regular + case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket + case .SYMBOLIC_LINK: fi.type = .Symlink + case .UNKNOWN: fi.type = .Undetermined + case: fi.type = .Undetermined + } + + fi.creation_time = time.Time{_nsec=i64(stat.ctim)} + fi.modification_time = time.Time{_nsec=i64(stat.mtim)} + fi.access_time = time.Time{_nsec=i64(stat.atim)} + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat, _err := wasi.fd_filestat_get(__fd(f)) + if _err != nil { + err = _get_platform_error(_err) + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} diff --git a/core/os/os2/temp_file_wasi.odin b/core/os/os2/temp_file_wasi.odin new file mode 100644 index 000000000..d5628d300 --- /dev/null +++ b/core/os/os2/temp_file_wasi.odin @@ -0,0 +1,9 @@ +#+private +package os2 + +import "base:runtime" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + // NOTE: requires user to add /tmp to their preopen dirs, no standard way exists. + return clone_string("/tmp", allocator) +} diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 003f8046d..1f0ac9287 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -1,3 +1,4 @@ +#+build !wasi package filepath import "core:os" diff --git a/core/path/filepath/path_wasi.odin b/core/path/filepath/path_wasi.odin new file mode 100644 index 000000000..74cc6ca1e --- /dev/null +++ b/core/path/filepath/path_wasi.odin @@ -0,0 +1,36 @@ +package filepath + +import "base:runtime" + +import "core:strings" + +SEPARATOR :: '/' +SEPARATOR_STRING :: `/` +LIST_SEPARATOR :: ':' + +is_reserved_name :: proc(path: string) -> bool { + return false +} + +is_abs :: proc(path: string) -> bool { + return strings.has_prefix(path, "/") +} + +abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { + if is_abs(path) { + return strings.clone(string(path), allocator), true + } + + return path, false +} + +join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { + for e, i in elems { + if e != "" { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return + return clean(p, allocator) + } + } + return "", nil +} diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 51dfa71d2..53b10eed7 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -1,3 +1,4 @@ +#+build !wasi package filepath import "core:os"