mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-09 06:23:14 +00:00
os/os2: wasi target support
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#+build !netbsd
|
||||
#+build !darwin
|
||||
#+build !js
|
||||
#+build !wasi
|
||||
package crypto
|
||||
|
||||
HAS_RAND_BYTES :: false
|
||||
|
||||
13
core/crypto/rand_wasi.odin
Normal file
13
core/crypto/rand_wasi.odin
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
|
||||
110
core/os/os2/dir_wasi.odin
Normal file
110
core/os/os2/dir_wasi.odin
Normal file
@@ -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^ = {}
|
||||
}
|
||||
186
core/os/os2/env_wasi.odin
Normal file
186
core/os/os2/env_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
47
core/os/os2/errors_wasi.odin
Normal file
47
core/os/os2/errors_wasi.odin
Normal file
@@ -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 "<unknown platform error>"
|
||||
}
|
||||
|
||||
_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)
|
||||
}
|
||||
}
|
||||
534
core/os/os2/file_wasi.odin
Normal file
534
core/os/os2/file_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
6
core/os/os2/heap_wasi.odin
Normal file
6
core/os/os2/heap_wasi.odin
Normal file
@@ -0,0 +1,6 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_heap_allocator_proc :: runtime.wasm_allocator_proc
|
||||
@@ -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()
|
||||
|
||||
84
core/os/os2/path_wasi.odin
Normal file
84
core/os/os2/path_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
13
core/os/os2/pipe_wasi.odin
Normal file
13
core/os/os2/pipe_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
89
core/os/os2/process_wasi.odin
Normal file
89
core/os/os2/process_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
101
core/os/os2/stat_wasi.odin
Normal file
101
core/os/os2/stat_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
9
core/os/os2/temp_file_wasi.odin
Normal file
9
core/os/os2/temp_file_wasi.odin
Normal file
@@ -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)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#+build !wasi
|
||||
package filepath
|
||||
|
||||
import "core:os"
|
||||
|
||||
36
core/path/filepath/path_wasi.odin
Normal file
36
core/path/filepath/path_wasi.odin
Normal file
@@ -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
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#+build !wasi
|
||||
package filepath
|
||||
|
||||
import "core:os"
|
||||
|
||||
Reference in New Issue
Block a user