summaryrefslogtreecommitdiff
path: root/vendor/github.com/tetratelabs/wazero
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2024-07-12 09:39:47 +0000
committerLibravatar GitHub <noreply@github.com>2024-07-12 09:39:47 +0000
commitcde2fb6244a791b3c5b746112e3a8be3a79f39a4 (patch)
tree6079d6fb66d90ffbe8c1623525bb86829c162459 /vendor/github.com/tetratelabs/wazero
parent[chore] Add interaction policy gtsmodels (#3075) (diff)
downloadgotosocial-cde2fb6244a791b3c5b746112e3a8be3a79f39a4.tar.xz
[feature] support processing of (many) more media types (#3090)
* initial work replacing our media decoding / encoding pipeline with ffprobe + ffmpeg * specify the video codec to use when generating static image from emoji * update go-storage library (fixes incompatibility after updating go-iotools) * maintain image aspect ratio when generating a thumbnail for it * update readme to show go-ffmpreg * fix a bunch of media tests, move filesize checking to callers of media manager for more flexibility * remove extra debug from error message * fix up incorrect function signatures * update PutFile to just use regular file copy, as changes are file is on separate partition * fix remaining tests, remove some unneeded tests now we're working with ffmpeg/ffprobe * update more tests, add more code comments * add utilities to generate processed emoji / media outputs * fix remaining tests * add test for opus media file, add license header to utility cmds * limit the number of concurrently available ffmpeg / ffprobe instances * reduce number of instances * further reduce number of instances * fix envparsing test with configuration variables * update docs and configuration with new media-{local,remote}-max-size variables
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero')
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go97
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go116
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go100
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go2016
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go239
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go44
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go55
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go22
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go188
-rw-r--r--vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go314
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go6
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go16
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go6
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go314
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go164
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go15
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go6
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go3
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go148
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go3
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go71
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go26
22 files changed, 3969 insertions, 0 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go
new file mode 100644
index 000000000..4c82e95e2
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go
@@ -0,0 +1,97 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// argsGet is the WASI function named ArgsGetName that reads command-line
+// argument data.
+//
+// # Parameters
+//
+// - argv: offset to begin writing argument offsets in uint32 little-endian
+// encoding to api.Memory
+// - argsSizesGet result argc * 4 bytes are written to this offset
+// - argvBuf: offset to write the null terminated arguments to api.Memory
+// - argsSizesGet result argv_len bytes are written to this offset
+//
+// Result (Errno)
+//
+// The return value is ErrnoSuccess except the following error conditions:
+// - sys.EFAULT: there is not enough memory to write results
+//
+// For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments:
+// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
+// to api.Memory:
+//
+// argvLen uint32le uint32le
+// +----------------+ +--------+ +--------+
+// | | | | | |
+// []byte{?, 'a', 0, 'b', 'c', 0, ?, 1, 0, 0, 0, 3, 0, 0, 0, ?}
+// argvBuf --^ ^ ^
+// argv --| |
+// offset that begins "a" --+ |
+// offset that begins "bc" --+
+//
+// See argsSizesGet
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
+// See https://en.wikipedia.org/wiki/Null-terminated_string
+var argsGet = newHostFunc(wasip1.ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
+
+func argsGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ argv, argvBuf := uint32(params[0]), uint32(params[1])
+ return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
+}
+
+// argsSizesGet is the WASI function named ArgsSizesGetName that reads
+// command-line argument sizes.
+//
+// # Parameters
+//
+// - resultArgc: offset to write the argument count to api.Memory
+// - resultArgvLen: offset to write the null-terminated argument length to
+// api.Memory
+//
+// Result (Errno)
+//
+// The return value is ErrnoSuccess except the following error conditions:
+// - sys.EFAULT: there is not enough memory to write results
+//
+// For example, if args are "a", "bc" and parameters resultArgc=1 and
+// resultArgvLen=6, this function writes the below to api.Memory:
+//
+// uint32le uint32le
+// +--------+ +--------+
+// | | | |
+// []byte{?, 2, 0, 0, 0, ?, 5, 0, 0, 0, ?}
+// resultArgc --^ ^
+// 2 args --+ |
+// resultArgvLen --|
+// len([]byte{'a',0,'b',c',0}) --+
+//
+// See argsGet
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
+// See https://en.wikipedia.org/wiki/Null-terminated_string
+var argsSizesGet = newHostFunc(wasip1.ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
+
+func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ mem := mod.Memory()
+ resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
+
+ // argc and argv_len offsets are not necessarily sequential, so we have to
+ // write them independently.
+ if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
+ return sys.EFAULT
+ }
+ if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
+ return sys.EFAULT
+ }
+ return 0
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go
new file mode 100644
index 000000000..31af91071
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go
@@ -0,0 +1,116 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// clockResGet is the WASI function named ClockResGetName that returns the
+// resolution of time values returned by clockTimeGet.
+//
+// # Parameters
+//
+// - id: clock ID to use
+// - resultResolution: offset to write the resolution to api.Memory
+// - the resolution is an uint64 little-endian encoding
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.ENOTSUP: the clock ID is not supported.
+// - sys.EINVAL: the clock ID is invalid.
+// - sys.EFAULT: there is not enough memory to write results
+//
+// For example, if the resolution is 100ns, this function writes the below to
+// api.Memory:
+//
+// uint64le
+// +-------------------------------------+
+// | |
+// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?}
+// resultResolution --^
+//
+// Note: This is similar to `clock_getres` in POSIX.
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
+// See https://linux.die.net/man/3/clock_getres
+var clockResGet = newHostFunc(wasip1.ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
+
+func clockResGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ id, resultResolution := uint32(params[0]), uint32(params[1])
+
+ var resolution uint64 // ns
+ switch id {
+ case wasip1.ClockIDRealtime:
+ resolution = uint64(sysCtx.WalltimeResolution())
+ case wasip1.ClockIDMonotonic:
+ resolution = uint64(sysCtx.NanotimeResolution())
+ default:
+ return sys.EINVAL
+ }
+
+ if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
+ return sys.EFAULT
+ }
+ return 0
+}
+
+// clockTimeGet is the WASI function named ClockTimeGetName that returns
+// the time value of a name (time.Now).
+//
+// # Parameters
+//
+// - id: clock ID to use
+// - precision: maximum lag (exclusive) that the returned time value may have,
+// compared to its actual value
+// - resultTimestamp: offset to write the timestamp to api.Memory
+// - the timestamp is epoch nanos encoded as a little-endian uint64
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.ENOTSUP: the clock ID is not supported.
+// - sys.EINVAL: the clock ID is invalid.
+// - sys.EFAULT: there is not enough memory to write results
+//
+// For example, if time.Now returned exactly midnight UTC 2022-01-01
+// (1640995200000000000), and parameters resultTimestamp=1, this function
+// writes the below to api.Memory:
+//
+// uint64le
+// +------------------------------------------+
+// | |
+// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?}
+// resultTimestamp --^
+//
+// Note: This is similar to `clock_gettime` in POSIX.
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
+// See https://linux.die.net/man/3/clock_gettime
+var clockTimeGet = newHostFunc(wasip1.ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
+
+func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ id := uint32(params[0])
+ // TODO: precision is currently ignored.
+ // precision = params[1]
+ resultTimestamp := uint32(params[2])
+
+ var val int64
+ switch id {
+ case wasip1.ClockIDRealtime:
+ val = sysCtx.WalltimeNanos()
+ case wasip1.ClockIDMonotonic:
+ val = sysCtx.Nanotime()
+ default:
+ return sys.EINVAL
+ }
+
+ if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
+ return sys.EFAULT
+ }
+ return 0
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go
new file mode 100644
index 000000000..ec8df708a
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go
@@ -0,0 +1,100 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// environGet is the WASI function named EnvironGetName that reads
+// environment variables.
+//
+// # Parameters
+//
+// - environ: offset to begin writing environment offsets in uint32
+// little-endian encoding to api.Memory
+// - environSizesGet result environc * 4 bytes are written to this offset
+// - environBuf: offset to write the null-terminated variables to api.Memory
+// - the format is like os.Environ: null-terminated "key=val" entries
+// - environSizesGet result environLen bytes are written to this offset
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EFAULT: there is not enough memory to write results
+//
+// For example, if environSizesGet wrote environc=2 and environLen=9 for
+// environment variables: "a=b", "b=cd" and parameters environ=11 and
+// environBuf=1, this function writes the below to api.Memory:
+//
+// environLen uint32le uint32le
+// +------------------------------------+ +--------+ +--------+
+// | | | | | |
+// []byte{?, 'a', '=', 'b', 0, 'b', '=', 'c', 'd', 0, ?, 1, 0, 0, 0, 5, 0, 0, 0, ?}
+// environBuf --^ ^ ^
+// environ offset for "a=b" --+ |
+// environ offset for "b=cd" --+
+//
+// See environSizesGet
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
+// See https://en.wikipedia.org/wiki/Null-terminated_string
+var environGet = newHostFunc(wasip1.EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf")
+
+func environGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ environ, environBuf := uint32(params[0]), uint32(params[1])
+
+ return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Environ(), environ, environBuf, sysCtx.EnvironSize())
+}
+
+// environSizesGet is the WASI function named EnvironSizesGetName that
+// reads environment variable sizes.
+//
+// # Parameters
+//
+// - resultEnvironc: offset to write the count of environment variables to
+// api.Memory
+// - resultEnvironvLen: offset to write the null-terminated environment
+// variable length to api.Memory
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EFAULT: there is not enough memory to write results
+//
+// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
+// resultEnvironvLen=6, this function writes the below to api.Memory:
+//
+// uint32le uint32le
+// +--------+ +--------+
+// | | | |
+// []byte{?, 2, 0, 0, 0, ?, 9, 0, 0, 0, ?}
+// resultEnvironc --^ ^
+// 2 variables --+ |
+// resultEnvironvLen --|
+// len([]byte{'a','=','b',0, |
+// 'b','=','c','d',0}) --+
+//
+// See environGet
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
+// and https://en.wikipedia.org/wiki/Null-terminated_string
+var environSizesGet = newHostFunc(wasip1.EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
+
+func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ mem := mod.Memory()
+ resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
+
+ // environc and environv_len offsets are not necessarily sequential, so we
+ // have to write them independently.
+ if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
+ return sys.EFAULT
+ }
+ if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
+ return sys.EFAULT
+ }
+ return 0
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go
new file mode 100644
index 000000000..1ec0d81b3
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go
@@ -0,0 +1,2016 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+ "io"
+ "io/fs"
+ "math"
+ "path"
+ "strings"
+ "unsafe"
+
+ "github.com/tetratelabs/wazero/api"
+ experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+ socketapi "github.com/tetratelabs/wazero/internal/sock"
+ "github.com/tetratelabs/wazero/internal/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+ sysapi "github.com/tetratelabs/wazero/sys"
+)
+
+// fdAdvise is the WASI function named FdAdviseName which provides file
+// advisory information on a file descriptor.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno
+var fdAdvise = newHostFunc(
+ wasip1.FdAdviseName, fdAdviseFn,
+ []wasm.ValueType{i32, i64, i64, i32},
+ "fd", "offset", "len", "advice",
+)
+
+func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd := int32(params[0])
+ _ = params[1]
+ _ = params[2]
+ advice := byte(params[3])
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ _, ok := fsc.LookupFile(fd)
+ if !ok {
+ return experimentalsys.EBADF
+ }
+
+ switch advice {
+ case wasip1.FdAdviceNormal,
+ wasip1.FdAdviceSequential,
+ wasip1.FdAdviceRandom,
+ wasip1.FdAdviceWillNeed,
+ wasip1.FdAdviceDontNeed,
+ wasip1.FdAdviceNoReuse:
+ default:
+ return experimentalsys.EINVAL
+ }
+
+ // FdAdvice corresponds to posix_fadvise, but it can only be supported on linux.
+ // However, the purpose of the call is just to do best-effort optimization on OS kernels,
+ // so just making this noop rather than returning NoSup error makes sense and doesn't affect
+ // the semantics of Wasm applications.
+ // TODO: invoke posix_fadvise on linux, and partially on darwin.
+ // - https://gitlab.com/cznic/fileutil/-/blob/v1.1.2/fileutil_linux.go#L87-95
+ // - https://github.com/bytecodealliance/system-interface/blob/62b97f9776b86235f318c3a6e308395a1187439b/src/fs/file_io_ext.rs#L430-L442
+ return 0
+}
+
+// fdAllocate is the WASI function named FdAllocateName which forces the
+// allocation of space in a file.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno
+var fdAllocate = newHostFunc(
+ wasip1.FdAllocateName, fdAllocateFn,
+ []wasm.ValueType{i32, i64, i64},
+ "fd", "offset", "len",
+)
+
+func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd := int32(params[0])
+ offset := params[1]
+ length := params[2]
+
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ f, ok := fsc.LookupFile(fd)
+ if !ok {
+ return experimentalsys.EBADF
+ }
+
+ tail := int64(offset + length)
+ if tail < 0 {
+ return experimentalsys.EINVAL
+ }
+
+ st, errno := f.File.Stat()
+ if errno != 0 {
+ return errno
+ }
+
+ if st.Size >= tail {
+ return 0 // We already have enough space.
+ }
+
+ return f.File.Truncate(tail)
+}
+
+// fdClose is the WASI function named FdCloseName which closes a file
+// descriptor.
+//
+// # Parameters
+//
+// - fd: file descriptor to close
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: the fd was not open.
+// - sys.ENOTSUP: the fs was a pre-open
+//
+// Note: This is similar to `close` in POSIX.
+// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
+// and https://linux.die.net/man/3/close
+var fdClose = newHostFunc(wasip1.FdCloseName, fdCloseFn, []api.ValueType{i32}, "fd")
+
+func fdCloseFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ fd := int32(params[0])
+
+ return fsc.CloseFile(fd)
+}
+
+// fdDatasync is the WASI function named FdDatasyncName which synchronizes
+// the data of a file to disk.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
+var fdDatasync = newHostFunc(wasip1.FdDatasyncName, fdDatasyncFn, []api.ValueType{i32}, "fd")
+
+func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ fd := int32(params[0])
+
+ // Check to see if the file descriptor is available
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else {
+ return f.File.Datasync()
+ }
+}
+
+// fdFdstatGet is the WASI function named FdFdstatGetName which returns the
+// attributes of a file descriptor.
+//
+// # Parameters
+//
+// - fd: file descriptor to get the fdstat attributes data
+// - resultFdstat: offset to write the result fdstat data
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EFAULT: `resultFdstat` points to an offset out of memory
+//
+// fdstat byte layout is 24-byte size, with the following fields:
+// - fs_filetype 1 byte: the file type
+// - fs_flags 2 bytes: the file descriptor flag
+// - 5 pad bytes
+// - fs_right_base 8 bytes: ignored as rights were removed from WASI.
+// - fs_right_inheriting 8 bytes: ignored as rights were removed from WASI.
+//
+// For example, with a file corresponding with `fd` was a directory (=3) opened
+// with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1,
+// this function writes the below to api.Memory:
+//
+// uint16le padding uint64le uint64le
+// uint8 --+ +--+ +-----------+ +--------------------+ +--------------------+
+// | | | | | | | | |
+// []byte{?, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}
+// resultFdstat --^ ^-- fs_flags ^-- fs_right_base ^-- fs_right_inheriting
+// |
+// +-- fs_filetype
+//
+// Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as
+// well as additional fields.
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat
+// and https://linux.die.net/man/3/fsync
+var fdFdstatGet = newHostFunc(wasip1.FdFdstatGetName, fdFdstatGetFn, []api.ValueType{i32, i32}, "fd", "result.stat")
+
+// fdFdstatGetFn cannot currently use proxyResultParams because fdstat is larger
+// than api.ValueTypeI64 (i64 == 8 bytes, but fdstat is 24).
+func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd, resultFdstat := int32(params[0]), uint32(params[1])
+
+ // Ensure we can write the fdstat
+ buf, ok := mod.Memory().Read(resultFdstat, 24)
+ if !ok {
+ return experimentalsys.EFAULT
+ }
+
+ var fdflags uint16
+ var st sysapi.Stat_t
+ var errno experimentalsys.Errno
+ f, ok := fsc.LookupFile(fd)
+ if !ok {
+ return experimentalsys.EBADF
+ } else if st, errno = f.File.Stat(); errno != 0 {
+ return errno
+ } else if f.File.IsAppend() {
+ fdflags |= wasip1.FD_APPEND
+ }
+
+ if f.File.IsNonblock() {
+ fdflags |= wasip1.FD_NONBLOCK
+ }
+
+ var fsRightsBase uint32
+ var fsRightsInheriting uint32
+ fileType := getExtendedWasiFiletype(f.File, st.Mode)
+
+ switch fileType {
+ case wasip1.FILETYPE_DIRECTORY:
+ // To satisfy wasi-testsuite, we must advertise that directories cannot
+ // be given seek permission (RIGHT_FD_SEEK).
+ fsRightsBase = dirRightsBase
+ fsRightsInheriting = fileRightsBase | dirRightsBase
+ case wasip1.FILETYPE_CHARACTER_DEVICE:
+ // According to wasi-libc,
+ // > A tty is a character device that we can't seek or tell on.
+ // See https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-bottom-half/sources/isatty.c#L13-L18
+ fsRightsBase = fileRightsBase &^ wasip1.RIGHT_FD_SEEK &^ wasip1.RIGHT_FD_TELL
+ default:
+ fsRightsBase = fileRightsBase
+ }
+
+ writeFdstat(buf, fileType, fdflags, fsRightsBase, fsRightsInheriting)
+ return 0
+}
+
+// isPreopenedStdio returns true if the FD is sys.FdStdin, sys.FdStdout or
+// sys.FdStderr and pre-opened. This double check is needed in case the guest
+// closes stdin and re-opens it with a random alternative file.
+//
+// Currently, we only support non-blocking mode for standard I/O streams.
+// Non-blocking mode is rarely supported for regular files, and we don't
+// yet have support for sockets, so we make a special case.
+//
+// Note: this to get or set FD_NONBLOCK, but skip FD_APPEND. Our current
+// implementation can't set FD_APPEND, without re-opening files. As stdio are
+// pre-opened, we don't know how to re-open them, neither should we close the
+// underlying file. Later, we could add support for setting FD_APPEND, similar
+// to SetNonblock.
+func isPreopenedStdio(fd int32, f *sys.FileEntry) bool {
+ return fd <= sys.FdStderr && f.IsPreopen
+}
+
+const fileRightsBase = wasip1.RIGHT_FD_DATASYNC |
+ wasip1.RIGHT_FD_READ |
+ wasip1.RIGHT_FD_SEEK |
+ wasip1.RIGHT_FDSTAT_SET_FLAGS |
+ wasip1.RIGHT_FD_SYNC |
+ wasip1.RIGHT_FD_TELL |
+ wasip1.RIGHT_FD_WRITE |
+ wasip1.RIGHT_FD_ADVISE |
+ wasip1.RIGHT_FD_ALLOCATE |
+ wasip1.RIGHT_FD_FILESTAT_GET |
+ wasip1.RIGHT_FD_FILESTAT_SET_SIZE |
+ wasip1.RIGHT_FD_FILESTAT_SET_TIMES |
+ wasip1.RIGHT_POLL_FD_READWRITE
+
+const dirRightsBase = wasip1.RIGHT_FD_DATASYNC |
+ wasip1.RIGHT_FDSTAT_SET_FLAGS |
+ wasip1.RIGHT_FD_SYNC |
+ wasip1.RIGHT_PATH_CREATE_DIRECTORY |
+ wasip1.RIGHT_PATH_CREATE_FILE |
+ wasip1.RIGHT_PATH_LINK_SOURCE |
+ wasip1.RIGHT_PATH_LINK_TARGET |
+ wasip1.RIGHT_PATH_OPEN |
+ wasip1.RIGHT_FD_READDIR |
+ wasip1.RIGHT_PATH_READLINK |
+ wasip1.RIGHT_PATH_RENAME_SOURCE |
+ wasip1.RIGHT_PATH_RENAME_TARGET |
+ wasip1.RIGHT_PATH_FILESTAT_GET |
+ wasip1.RIGHT_PATH_FILESTAT_SET_SIZE |
+ wasip1.RIGHT_PATH_FILESTAT_SET_TIMES |
+ wasip1.RIGHT_FD_FILESTAT_GET |
+ wasip1.RIGHT_FD_FILESTAT_SET_TIMES |
+ wasip1.RIGHT_PATH_SYMLINK |
+ wasip1.RIGHT_PATH_REMOVE_DIRECTORY |
+ wasip1.RIGHT_PATH_UNLINK_FILE
+
+func writeFdstat(buf []byte, fileType uint8, fdflags uint16, fsRightsBase, fsRightsInheriting uint32) {
+ b := (*[24]byte)(buf)
+ le.PutUint16(b[0:], uint16(fileType))
+ le.PutUint16(b[2:], fdflags)
+ le.PutUint32(b[4:], 0)
+ le.PutUint64(b[8:], uint64(fsRightsBase))
+ le.PutUint64(b[16:], uint64(fsRightsInheriting))
+}
+
+// fdFdstatSetFlags is the WASI function named FdFdstatSetFlagsName which
+// adjusts the flags associated with a file descriptor.
+var fdFdstatSetFlags = newHostFunc(wasip1.FdFdstatSetFlagsName, fdFdstatSetFlagsFn, []wasm.ValueType{i32, i32}, "fd", "flags")
+
+func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd, wasiFlag := int32(params[0]), uint16(params[1])
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ // Currently we only support APPEND and NONBLOCK.
+ if wasip1.FD_DSYNC&wasiFlag != 0 || wasip1.FD_RSYNC&wasiFlag != 0 || wasip1.FD_SYNC&wasiFlag != 0 {
+ return experimentalsys.EINVAL
+ }
+
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else {
+ nonblock := wasip1.FD_NONBLOCK&wasiFlag != 0
+ errno := f.File.SetNonblock(nonblock)
+ if errno != 0 {
+ return errno
+ }
+ if stat, err := f.File.Stat(); err == 0 && stat.Mode.IsRegular() {
+ // For normal files, proceed to apply an append flag.
+ append := wasip1.FD_APPEND&wasiFlag != 0
+ return f.File.SetAppend(append)
+ }
+ }
+
+ return 0
+}
+
+// fdFdstatSetRights will not be implemented as rights were removed from WASI.
+//
+// See https://github.com/bytecodealliance/wasmtime/pull/4666
+var fdFdstatSetRights = stubFunction(
+ wasip1.FdFdstatSetRightsName,
+ []wasm.ValueType{i32, i64, i64},
+ "fd", "fs_rights_base", "fs_rights_inheriting",
+)
+
+// fdFilestatGet is the WASI function named FdFilestatGetName which returns
+// the stat attributes of an open file.
+//
+// # Parameters
+//
+// - fd: file descriptor to get the filestat attributes data for
+// - resultFilestat: offset to write the result filestat data
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EIO: could not stat `fd` on filesystem
+// - sys.EFAULT: `resultFilestat` points to an offset out of memory
+//
+// filestat byte layout is 64-byte size, with the following fields:
+// - dev 8 bytes: the device ID of device containing the file
+// - ino 8 bytes: the file serial number
+// - filetype 1 byte: the type of the file
+// - 7 pad bytes
+// - nlink 8 bytes: number of hard links to the file
+// - size 8 bytes: for regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link
+// - atim 8 bytes: ast data access timestamp
+// - mtim 8 bytes: last data modification timestamp
+// - ctim 8 bytes: ast file status change timestamp
+//
+// For example, with a regular file this function writes the below to api.Memory:
+//
+// uint8 --+
+// uint64le uint64le | padding uint64le uint64le uint64le uint64le uint64le
+// +--------------------+ +--------------------+ | +-----------------+ +--------------------+ +-----------------------+ +----------------------------------+ +----------------------------------+ +----------------------------------+
+// | | | | | | | | | | | | | | | | |
+// []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 80, 0, 0, 0, 0, 0, 0, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23}
+// resultFilestat ^-- dev ^-- ino ^ ^-- nlink ^-- size ^-- atim ^-- mtim ^-- ctim
+// |
+// +-- filetype
+//
+// The following properties of filestat are not implemented:
+// - dev: not supported by Golang FS
+// - ino: not supported by Golang FS
+// - nlink: not supported by Golang FS, we use 1
+// - atime: not supported by Golang FS, we use mtim for this
+// - ctim: not supported by Golang FS, we use mtim for this
+//
+// Note: This is similar to `fstat` in POSIX.
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat
+// and https://linux.die.net/man/3/fstat
+var fdFilestatGet = newHostFunc(wasip1.FdFilestatGetName, fdFilestatGetFn, []api.ValueType{i32, i32}, "fd", "result.filestat")
+
+// fdFilestatGetFn cannot currently use proxyResultParams because filestat is
+// larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64).
+func fdFilestatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ return fdFilestatGetFunc(mod, int32(params[0]), uint32(params[1]))
+}
+
+func fdFilestatGetFunc(mod api.Module, fd int32, resultBuf uint32) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ // Ensure we can write the filestat
+ buf, ok := mod.Memory().Read(resultBuf, 64)
+ if !ok {
+ return experimentalsys.EFAULT
+ }
+
+ f, ok := fsc.LookupFile(fd)
+ if !ok {
+ return experimentalsys.EBADF
+ }
+
+ st, errno := f.File.Stat()
+ if errno != 0 {
+ return errno
+ }
+
+ filetype := getExtendedWasiFiletype(f.File, st.Mode)
+ return writeFilestat(buf, &st, filetype)
+}
+
+func getExtendedWasiFiletype(file experimentalsys.File, fm fs.FileMode) (ftype uint8) {
+ ftype = getWasiFiletype(fm)
+ if ftype == wasip1.FILETYPE_UNKNOWN {
+ if _, ok := file.(socketapi.TCPSock); ok {
+ ftype = wasip1.FILETYPE_SOCKET_STREAM
+ } else if _, ok = file.(socketapi.TCPConn); ok {
+ ftype = wasip1.FILETYPE_SOCKET_STREAM
+ }
+ }
+ return
+}
+
+func getWasiFiletype(fm fs.FileMode) uint8 {
+ switch {
+ case fm.IsRegular():
+ return wasip1.FILETYPE_REGULAR_FILE
+ case fm.IsDir():
+ return wasip1.FILETYPE_DIRECTORY
+ case fm&fs.ModeSymlink != 0:
+ return wasip1.FILETYPE_SYMBOLIC_LINK
+ case fm&fs.ModeDevice != 0:
+ // Unlike ModeDevice and ModeCharDevice, FILETYPE_CHARACTER_DEVICE and
+ // FILETYPE_BLOCK_DEVICE are set mutually exclusively.
+ if fm&fs.ModeCharDevice != 0 {
+ return wasip1.FILETYPE_CHARACTER_DEVICE
+ }
+ return wasip1.FILETYPE_BLOCK_DEVICE
+ default: // unknown
+ return wasip1.FILETYPE_UNKNOWN
+ }
+}
+
+func writeFilestat(buf []byte, st *sysapi.Stat_t, ftype uint8) (errno experimentalsys.Errno) {
+ le.PutUint64(buf, st.Dev)
+ le.PutUint64(buf[8:], st.Ino)
+ le.PutUint64(buf[16:], uint64(ftype))
+ le.PutUint64(buf[24:], st.Nlink)
+ le.PutUint64(buf[32:], uint64(st.Size))
+ le.PutUint64(buf[40:], uint64(st.Atim))
+ le.PutUint64(buf[48:], uint64(st.Mtim))
+ le.PutUint64(buf[56:], uint64(st.Ctim))
+ return
+}
+
+// fdFilestatSetSize is the WASI function named FdFilestatSetSizeName which
+// adjusts the size of an open file.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno
+var fdFilestatSetSize = newHostFunc(wasip1.FdFilestatSetSizeName, fdFilestatSetSizeFn, []wasm.ValueType{i32, i64}, "fd", "size")
+
+func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd := int32(params[0])
+ size := int64(params[1])
+
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ // Check to see if the file descriptor is available
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else {
+ return f.File.Truncate(size)
+ }
+}
+
+// fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes
+// which adjusts the times of an open file.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_timesfd-fd-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
+var fdFilestatSetTimes = newHostFunc(
+ wasip1.FdFilestatSetTimesName, fdFilestatSetTimesFn,
+ []wasm.ValueType{i32, i64, i64, i32},
+ "fd", "atim", "mtim", "fst_flags",
+)
+
+func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd := int32(params[0])
+ atim := int64(params[1])
+ mtim := int64(params[2])
+ fstFlags := uint16(params[3])
+
+ sys := mod.(*wasm.ModuleInstance).Sys
+ fsc := sys.FS()
+
+ f, ok := fsc.LookupFile(fd)
+ if !ok {
+ return experimentalsys.EBADF
+ }
+
+ atim, mtim, errno := toTimes(sys.WalltimeNanos, atim, mtim, fstFlags)
+ if errno != 0 {
+ return errno
+ }
+
+ // Try to update the file timestamps by file-descriptor.
+ errno = f.File.Utimens(atim, mtim)
+
+ // Fall back to path based, despite it being less precise.
+ switch errno {
+ case experimentalsys.EPERM, experimentalsys.ENOSYS:
+ errno = f.FS.Utimens(f.Name, atim, mtim)
+ }
+
+ return errno
+}
+
+func toTimes(walltime func() int64, atim, mtim int64, fstFlags uint16) (int64, int64, experimentalsys.Errno) {
+ // times[0] == atim, times[1] == mtim
+
+ var nowTim int64
+
+ // coerce atim into a timespec
+ if set, now := fstFlags&wasip1.FstflagsAtim != 0, fstFlags&wasip1.FstflagsAtimNow != 0; set && now {
+ return 0, 0, experimentalsys.EINVAL
+ } else if set {
+ // atim is already correct
+ } else if now {
+ nowTim = walltime()
+ atim = nowTim
+ } else {
+ atim = experimentalsys.UTIME_OMIT
+ }
+
+ // coerce mtim into a timespec
+ if set, now := fstFlags&wasip1.FstflagsMtim != 0, fstFlags&wasip1.FstflagsMtimNow != 0; set && now {
+ return 0, 0, experimentalsys.EINVAL
+ } else if set {
+ // mtim is already correct
+ } else if now {
+ if nowTim != 0 {
+ mtim = nowTim
+ } else {
+ mtim = walltime()
+ }
+ } else {
+ mtim = experimentalsys.UTIME_OMIT
+ }
+ return atim, mtim, 0
+}
+
+// fdPread is the WASI function named FdPreadName which reads from a file
+// descriptor, without using and updating the file descriptor's offset.
+//
+// Except for handling offset, this implementation is identical to fdRead.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size
+var fdPread = newHostFunc(
+ wasip1.FdPreadName, fdPreadFn,
+ []api.ValueType{i32, i32, i32, i64, i32},
+ "fd", "iovs", "iovs_len", "offset", "result.nread",
+)
+
+func fdPreadFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ return fdReadOrPread(mod, params, true)
+}
+
+// fdPrestatGet is the WASI function named FdPrestatGetName which returns
+// the prestat data of a file descriptor.
+//
+// # Parameters
+//
+// - fd: file descriptor to get the prestat
+// - resultPrestat: offset to write the result prestat data
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid or the `fd` is not a pre-opened directory
+// - sys.EFAULT: `resultPrestat` points to an offset out of memory
+//
+// prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes.
+// The only valid tag is `prestat_dir`, which is tag zero. This simplifies the
+// byte layout to 4 empty bytes followed by the uint32le encoded path length.
+//
+// For example, the directory name corresponding with `fd` was "/tmp" and
+// parameter resultPrestat=1, this function writes the below to api.Memory:
+//
+// padding uint32le
+// uint8 --+ +-----+ +--------+
+// | | | | |
+// []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?}
+// resultPrestat --^ ^
+// tag --+ |
+// +-- size in bytes of the string "/tmp"
+//
+// See fdPrestatDirName and
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
+var fdPrestatGet = newHostFunc(wasip1.FdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat")
+
+func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ fd, resultPrestat := int32(params[0]), uint32(params[1])
+
+ name, errno := preopenPath(fsc, fd)
+ if errno != 0 {
+ return errno
+ }
+
+ // Upper 32-bits are zero because...
+ // * Zero-value 8-bit tag, and 3-byte zero-value padding
+ prestat := uint64(len(name) << 32)
+ if !mod.Memory().WriteUint64Le(resultPrestat, prestat) {
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+// fdPrestatDirName is the WASI function named FdPrestatDirNameName which
+// returns the path of the pre-opened directory of a file descriptor.
+//
+// # Parameters
+//
+// - fd: file descriptor to get the path of the pre-opened directory
+// - path: offset in api.Memory to write the result path
+// - pathLen: count of bytes to write to `path`
+// - This should match the uint32le fdPrestatGet writes to offset
+// `resultPrestat`+4
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EFAULT: `path` points to an offset out of memory
+// - sys.ENAMETOOLONG: `pathLen` is longer than the actual length of the result
+//
+// For example, the directory name corresponding with `fd` was "/tmp" and
+// # Parameters path=1 pathLen=4 (correct), this function will write the below to
+// api.Memory:
+//
+// pathLen
+// +--------------+
+// | |
+// []byte{?, '/', 't', 'm', 'p', ?}
+// path --^
+//
+// See fdPrestatGet
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name
+var fdPrestatDirName = newHostFunc(
+ wasip1.FdPrestatDirNameName, fdPrestatDirNameFn,
+ []api.ValueType{i32, i32, i32},
+ "fd", "result.path", "result.path_len",
+)
+
+func fdPrestatDirNameFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ fd, path, pathLen := int32(params[0]), uint32(params[1]), uint32(params[2])
+
+ name, errno := preopenPath(fsc, fd)
+ if errno != 0 {
+ return errno
+ }
+
+ // Some runtimes may have another semantics. See /RATIONALE.md
+ if uint32(len(name)) < pathLen {
+ return experimentalsys.ENAMETOOLONG
+ }
+
+ if !mod.Memory().Write(path, []byte(name)[:pathLen]) {
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+// fdPwrite is the WASI function named FdPwriteName which writes to a file
+// descriptor, without using and updating the file descriptor's offset.
+//
+// Except for handling offset, this implementation is identical to fdWrite.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size
+var fdPwrite = newHostFunc(
+ wasip1.FdPwriteName, fdPwriteFn,
+ []api.ValueType{i32, i32, i32, i64, i32},
+ "fd", "iovs", "iovs_len", "offset", "result.nwritten",
+)
+
+func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ return fdWriteOrPwrite(mod, params, true)
+}
+
+// fdRead is the WASI function named FdReadName which reads from a file
+// descriptor.
+//
+// # Parameters
+//
+// - fd: an opened file descriptor to read data from
+// - iovs: offset in api.Memory to read offset, size pairs representing where
+// to write file data
+// - Both offset and length are encoded as uint32le
+// - iovsCount: count of memory offset, size pairs to read sequentially
+// starting at iovs
+// - resultNread: offset in api.Memory to write the number of bytes read
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EFAULT: `iovs` or `resultNread` point to an offset out of memory
+// - sys.EIO: a file system error
+//
+// For example, this function needs to first read `iovs` to determine where
+// to write contents. If parameters iovs=1 iovsCount=2, this function reads two
+// offset/length pairs from api.Memory:
+//
+// iovs[0] iovs[1]
+// +---------------------+ +--------------------+
+// | uint32le uint32le| |uint32le uint32le|
+// +---------+ +--------+ +--------+ +--------+
+// | | | | | | | |
+// []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
+// iovs --^ ^ ^ ^
+// | | | |
+// offset --+ length --+ offset --+ length --+
+//
+// If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter
+// resultNread=26, this function writes the below to api.Memory:
+//
+// iovs[0].length iovs[1].length
+// +--------------+ +----+ uint32le
+// | | | | +--------+
+// []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 }
+// iovs[0].offset --^ ^ ^
+// iovs[1].offset --+ |
+// resultNread --+
+//
+// Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv
+//
+// See fdWrite
+// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
+var fdRead = newHostFunc(
+ wasip1.FdReadName, fdReadFn,
+ []api.ValueType{i32, i32, i32, i32},
+ "fd", "iovs", "iovs_len", "result.nread",
+)
+
+// preader tracks an offset across multiple reads.
+type preader struct {
+ f experimentalsys.File
+ offset int64
+}
+
+// Read implements the same function as documented on sys.File.
+func (w *preader) Read(buf []byte) (n int, errno experimentalsys.Errno) {
+ if len(buf) == 0 {
+ return 0, 0 // less overhead on zero-length reads.
+ }
+
+ n, err := w.f.Pread(buf, w.offset)
+ w.offset += int64(n)
+ return n, err
+}
+
+func fdReadFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ return fdReadOrPread(mod, params, false)
+}
+
+func fdReadOrPread(mod api.Module, params []uint64, isPread bool) experimentalsys.Errno {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ iovs := uint32(params[1])
+ iovsCount := uint32(params[2])
+
+ var resultNread uint32
+ var reader func(buf []byte) (n int, errno experimentalsys.Errno)
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else if isPread {
+ offset := int64(params[3])
+ reader = (&preader{f: f.File, offset: offset}).Read
+ resultNread = uint32(params[4])
+ } else {
+ reader = f.File.Read
+ resultNread = uint32(params[3])
+ }
+
+ nread, errno := readv(mem, iovs, iovsCount, reader)
+ if errno != 0 {
+ return errno
+ }
+ if !mem.WriteUint32Le(resultNread, nread) {
+ return experimentalsys.EFAULT
+ } else {
+ return 0
+ }
+}
+
+func readv(mem api.Memory, iovs uint32, iovsCount uint32, reader func(buf []byte) (nread int, errno experimentalsys.Errno)) (uint32, experimentalsys.Errno) {
+ var nread uint32
+ iovsStop := iovsCount << 3 // iovsCount * 8
+ iovsBuf, ok := mem.Read(iovs, iovsStop)
+ if !ok {
+ return 0, experimentalsys.EFAULT
+ }
+
+ for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
+ offset := le.Uint32(iovsBuf[iovsPos:])
+ l := le.Uint32(iovsBuf[iovsPos+4:])
+
+ if l == 0 { // A zero length iovec could be ahead of another.
+ continue
+ }
+
+ b, ok := mem.Read(offset, l)
+ if !ok {
+ return 0, experimentalsys.EFAULT
+ }
+
+ n, errno := reader(b)
+ nread += uint32(n)
+
+ if errno == experimentalsys.ENOSYS {
+ return 0, experimentalsys.EBADF // e.g. unimplemented for read
+ } else if errno != 0 {
+ return 0, errno
+ } else if n < int(l) {
+ break // stop when we read less than capacity.
+ }
+ }
+ return nread, 0
+}
+
+// fdReaddir is the WASI function named wasip1.FdReaddirName which reads
+// directory entries from a directory. Special behaviors required by this
+// function are implemented in sys.DirentCache.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size
+//
+// # Result (Errno)
+//
+// The return value is 0 except the following known error conditions:
+// - sys.ENOSYS: the implementation does not support this function.
+// - sys.EBADF: the file was closed or not a directory.
+// - sys.EFAULT: `buf` or `buf_len` point to an offset out of memory.
+// - sys.ENOENT: `cookie` was invalid.
+// - sys.EINVAL: `buf_len` was not large enough to write a dirent header.
+//
+// # End of Directory (EOF)
+//
+// More entries are available when `result.bufused` == `buf_len`. See
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir
+// https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44
+var fdReaddir = newHostFunc(
+ wasip1.FdReaddirName, fdReaddirFn,
+ []wasm.ValueType{i32, i32, i32, i64, i32},
+ "fd", "buf", "buf_len", "cookie", "result.bufused",
+)
+
+func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ buf := uint32(params[1])
+ bufLen := uint32(params[2])
+ cookie := params[3]
+ resultBufused := uint32(params[4])
+
+ // The bufLen must be enough to write a dirent header.
+ if bufLen < wasip1.DirentSize {
+ // This is a bug in the caller, as unless `buf_len` is large enough to
+ // write a dirent, it can't read the `d_namlen` from it.
+ return experimentalsys.EINVAL
+ }
+
+ // Get or open a dirent cache for this file descriptor.
+ dir, errno := direntCache(fsc, fd)
+ if errno != 0 {
+ return errno
+ }
+
+ // First, determine the maximum directory entries that can be encoded as
+ // dirents. The total size is DirentSize(24) + nameSize, for each file.
+ // Since a zero-length file name is invalid, the minimum size entry is
+ // 25 (DirentSize + 1 character).
+ maxDirEntries := bufLen/wasip1.DirentSize + 1
+
+ // While unlikely maxDirEntries will fit into bufLen, add one more just in
+ // case, as we need to know if we hit the end of the directory or not to
+ // write the correct bufused (e.g. == bufLen unless EOF).
+ // >> If less than the size of the read buffer, the end of the
+ // >> directory has been reached.
+ maxDirEntries += 1
+
+ // Read up to max entries. The underlying implementation will cache these,
+ // starting at the current location, so that they can be re-read. This is
+ // important because even the first could end up larger than bufLen due to
+ // the size of its name.
+ dirents, errno := dir.Read(cookie, maxDirEntries)
+ if errno != 0 {
+ return errno
+ }
+
+ // Determine how many dirents we can write, including a potentially
+ // truncated last entry.
+ bufToWrite, direntCount, truncatedLen := maxDirents(dirents, bufLen)
+
+ // Now, write entries to the underlying buffer.
+ if bufToWrite > 0 {
+
+ // d_next is the index of the next file in the list, so it should
+ // always be one higher than the requested cookie.
+ d_next := cookie + 1
+ // ^^ yes this can overflow to negative, which means our implementation
+ // doesn't support writing greater than max int64 entries.
+
+ buf, ok := mem.Read(buf, bufToWrite)
+ if !ok {
+ return experimentalsys.EFAULT
+ }
+
+ writeDirents(buf, dirents, d_next, direntCount, truncatedLen)
+ }
+
+ // bufused == bufLen means more dirents exist, which is the case when one
+ // is truncated.
+ bufused := bufToWrite
+ if truncatedLen > 0 {
+ bufused = bufLen
+ }
+
+ if !mem.WriteUint32Le(resultBufused, bufused) {
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+const largestDirent = int64(math.MaxUint32 - wasip1.DirentSize)
+
+// maxDirents returns the dirents to write.
+//
+// `bufToWrite` is the amount of memory needed to write direntCount, which
+// includes up to wasip1.DirentSize of a last truncated entry.
+func maxDirents(dirents []experimentalsys.Dirent, bufLen uint32) (bufToWrite uint32, direntCount int, truncatedLen uint32) {
+ lenRemaining := bufLen
+ for i := range dirents {
+ if lenRemaining == 0 {
+ break
+ }
+ d := dirents[i]
+ direntCount++
+
+ // use int64 to guard against huge filenames
+ nameLen := int64(len(d.Name))
+ var entryLen uint32
+
+ // Check to see if DirentSize + nameLen overflows, or if it would be
+ // larger than possible to encode.
+ if el := int64(wasip1.DirentSize) + nameLen; el < 0 || el > largestDirent {
+ // panic, as testing is difficult. ex we would have to extract a
+ // function to get size of a string or allocate a 2^32 size one!
+ panic("invalid filename: too large")
+ } else { // we know this can fit into a uint32
+ entryLen = uint32(el)
+ }
+
+ if entryLen > lenRemaining {
+ // We haven't room to write the entry, and docs say to write the
+ // header. This helps especially when there is an entry with a very
+ // long filename. Ex if bufLen is 4096 and the filename is 4096,
+ // we need to write DirentSize(24) + 4096 bytes to write the entry.
+ // In this case, we only write up to DirentSize(24) to allow the
+ // caller to resize.
+ if lenRemaining >= wasip1.DirentSize {
+ truncatedLen = wasip1.DirentSize
+ } else {
+ truncatedLen = lenRemaining
+ }
+ bufToWrite += truncatedLen
+ break
+ }
+
+ // This won't go negative because we checked entryLen <= lenRemaining.
+ lenRemaining -= entryLen
+ bufToWrite += entryLen
+ }
+ return
+}
+
+// writeDirents writes the directory entries to the buffer, which is pre-sized
+// based on maxDirents. truncatedEntryLen means the last is written without its
+// name.
+func writeDirents(buf []byte, dirents []experimentalsys.Dirent, d_next uint64, direntCount int, truncatedLen uint32) {
+ pos := uint32(0)
+ skipNameI := -1
+
+ // If the last entry was truncated, we either skip it or write it without
+ // its name, depending on the length.
+ if truncatedLen > 0 {
+ if truncatedLen < wasip1.DirentSize {
+ direntCount-- // skip as too small to write the header.
+ } else {
+ skipNameI = direntCount - 1 // write the header, but not the name.
+ }
+ }
+
+ for i := 0; i < direntCount; i++ {
+ e := dirents[i]
+ nameLen := uint32(len(e.Name))
+ writeDirent(buf[pos:], d_next, e.Ino, nameLen, e.Type)
+ d_next++
+ pos += wasip1.DirentSize
+
+ if i != skipNameI {
+ copy(buf[pos:], e.Name)
+ pos += nameLen
+ }
+ }
+}
+
+// writeDirent writes DirentSize bytes
+func writeDirent(buf []byte, dNext uint64, ino sysapi.Inode, dNamlen uint32, dType fs.FileMode) {
+ le.PutUint64(buf, dNext) // d_next
+ le.PutUint64(buf[8:], ino) // d_ino
+ le.PutUint32(buf[16:], dNamlen) // d_namlen
+ filetype := getWasiFiletype(dType)
+ le.PutUint32(buf[20:], uint32(filetype)) // d_type
+}
+
+// direntCache lazy opens a sys.DirentCache for this directory or returns an
+// error.
+func direntCache(fsc *sys.FSContext, fd int32) (*sys.DirentCache, experimentalsys.Errno) {
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return nil, experimentalsys.EBADF
+ } else if dir, errno := f.DirentCache(); errno == 0 {
+ return dir, 0
+ } else if errno == experimentalsys.ENOTDIR {
+ // fd_readdir docs don't indicate whether to return sys.ENOTDIR or
+ // sys.EBADF. It has been noticed that rust will crash on sys.ENOTDIR,
+ // and POSIX C ref seems to not return this, so we don't either.
+ //
+ // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir
+ // and https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/dirent.h
+ return nil, experimentalsys.EBADF
+ } else {
+ return nil, errno
+ }
+}
+
+// fdRenumber is the WASI function named FdRenumberName which atomically
+// replaces a file descriptor by renumbering another file descriptor.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
+var fdRenumber = newHostFunc(wasip1.FdRenumberName, fdRenumberFn, []wasm.ValueType{i32, i32}, "fd", "to")
+
+func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ from := int32(params[0])
+ to := int32(params[1])
+
+ if errno := fsc.Renumber(from, to); errno != 0 {
+ return errno
+ }
+ return 0
+}
+
+// fdSeek is the WASI function named FdSeekName which moves the offset of a
+// file descriptor.
+//
+// # Parameters
+//
+// - fd: file descriptor to move the offset of
+// - offset: signed int64, which is encoded as uint64, input argument to
+// `whence`, which results in a new offset
+// - whence: operator that creates the new offset, given `offset` bytes
+// - If io.SeekStart, new offset == `offset`.
+// - If io.SeekCurrent, new offset == existing offset + `offset`.
+// - If io.SeekEnd, new offset == file size of `fd` + `offset`.
+// - resultNewoffset: offset in api.Memory to write the new offset to,
+// relative to start of the file
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EFAULT: `resultNewoffset` points to an offset out of memory
+// - sys.EINVAL: `whence` is an invalid value
+// - sys.EIO: a file system error
+// - sys.EISDIR: the file was a directory.
+//
+// For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4,
+// whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below
+// to api.Memory:
+//
+// uint64le
+// +--------------------+
+// | |
+// []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? }
+// resultNewoffset --^
+//
+// Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek
+//
+// See io.Seeker
+// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek
+var fdSeek = newHostFunc(
+ wasip1.FdSeekName, fdSeekFn,
+ []api.ValueType{i32, i64, i32, i32},
+ "fd", "offset", "whence", "result.newoffset",
+)
+
+func fdSeekFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ fd := int32(params[0])
+ offset := params[1]
+ whence := uint32(params[2])
+ resultNewoffset := uint32(params[3])
+
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else if isDir, _ := f.File.IsDir(); isDir {
+ return experimentalsys.EISDIR // POSIX doesn't forbid seeking a directory, but wasi-testsuite does.
+ } else if newOffset, errno := f.File.Seek(int64(offset), int(whence)); errno != 0 {
+ return errno
+ } else if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) {
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+// fdSync is the WASI function named FdSyncName which synchronizes the data
+// and metadata of a file to disk.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
+var fdSync = newHostFunc(wasip1.FdSyncName, fdSyncFn, []api.ValueType{i32}, "fd")
+
+func fdSyncFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ fd := int32(params[0])
+
+ // Check to see if the file descriptor is available
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else {
+ return f.File.Sync()
+ }
+}
+
+// fdTell is the WASI function named FdTellName which returns the current
+// offset of a file descriptor.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize
+var fdTell = newHostFunc(wasip1.FdTellName, fdTellFn, []api.ValueType{i32, i32}, "fd", "result.offset")
+
+func fdTellFn(ctx context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd := params[0]
+ offset := uint64(0)
+ whence := uint64(io.SeekCurrent)
+ resultNewoffset := params[1]
+
+ fdSeekParams := []uint64{fd, offset, whence, resultNewoffset}
+ return fdSeekFn(ctx, mod, fdSeekParams)
+}
+
+// fdWrite is the WASI function named FdWriteName which writes to a file
+// descriptor.
+//
+// # Parameters
+//
+// - fd: an opened file descriptor to write data to
+// - iovs: offset in api.Memory to read offset, size pairs representing the
+// data to write to `fd`
+// - Both offset and length are encoded as uint32le.
+// - iovsCount: count of memory offset, size pairs to read sequentially
+// starting at iovs
+// - resultNwritten: offset in api.Memory to write the number of bytes
+// written
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EFAULT: `iovs` or `resultNwritten` point to an offset out of memory
+// - sys.EIO: a file system error
+//
+// For example, this function needs to first read `iovs` to determine what to
+// write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two
+// offset/length pairs from api.Memory:
+//
+// iovs[0] iovs[1]
+// +---------------------+ +--------------------+
+// | uint32le uint32le| |uint32le uint32le|
+// +---------+ +--------+ +--------+ +--------+
+// | | | | | | | |
+// []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
+// iovs --^ ^ ^ ^
+// | | | |
+// offset --+ length --+ offset --+ length --+
+//
+// This function reads those chunks api.Memory into the `fd` sequentially.
+//
+// iovs[0].length iovs[1].length
+// +--------------+ +----+
+// | | | |
+// []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? }
+// iovs[0].offset --^ ^
+// iovs[1].offset --+
+//
+// Since "wazero" was written, if parameter resultNwritten=26, this function
+// writes the below to api.Memory:
+//
+// uint32le
+// +--------+
+// | |
+// []byte{ 0..24, ?, 6, 0, 0, 0', ? }
+// resultNwritten --^
+//
+// Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev
+//
+// See fdRead
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
+// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
+var fdWrite = newHostFunc(
+ wasip1.FdWriteName, fdWriteFn,
+ []api.ValueType{i32, i32, i32, i32},
+ "fd", "iovs", "iovs_len", "result.nwritten",
+)
+
+func fdWriteFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ return fdWriteOrPwrite(mod, params, false)
+}
+
+// pwriter tracks an offset across multiple writes.
+type pwriter struct {
+ f experimentalsys.File
+ offset int64
+}
+
+// Write implements the same function as documented on sys.File.
+func (w *pwriter) Write(buf []byte) (n int, errno experimentalsys.Errno) {
+ if len(buf) == 0 {
+ return 0, 0 // less overhead on zero-length writes.
+ }
+
+ n, err := w.f.Pwrite(buf, w.offset)
+ w.offset += int64(n)
+ return n, err
+}
+
+func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) experimentalsys.Errno {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ iovs := uint32(params[1])
+ iovsCount := uint32(params[2])
+
+ var resultNwritten uint32
+ var writer func(buf []byte) (n int, errno experimentalsys.Errno)
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return experimentalsys.EBADF
+ } else if isPwrite {
+ offset := int64(params[3])
+ writer = (&pwriter{f: f.File, offset: offset}).Write
+ resultNwritten = uint32(params[4])
+ } else {
+ writer = f.File.Write
+ resultNwritten = uint32(params[3])
+ }
+
+ nwritten, errno := writev(mem, iovs, iovsCount, writer)
+ if errno != 0 {
+ return errno
+ }
+
+ if !mod.Memory().WriteUint32Le(resultNwritten, nwritten) {
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+func writev(mem api.Memory, iovs uint32, iovsCount uint32, writer func(buf []byte) (n int, errno experimentalsys.Errno)) (uint32, experimentalsys.Errno) {
+ var nwritten uint32
+ iovsStop := iovsCount << 3 // iovsCount * 8
+ iovsBuf, ok := mem.Read(iovs, iovsStop)
+ if !ok {
+ return 0, experimentalsys.EFAULT
+ }
+
+ for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
+ offset := le.Uint32(iovsBuf[iovsPos:])
+ l := le.Uint32(iovsBuf[iovsPos+4:])
+
+ b, ok := mem.Read(offset, l)
+ if !ok {
+ return 0, experimentalsys.EFAULT
+ }
+ n, errno := writer(b)
+ nwritten += uint32(n)
+ if errno == experimentalsys.ENOSYS {
+ return 0, experimentalsys.EBADF // e.g. unimplemented for write
+ } else if errno != 0 {
+ return 0, errno
+ }
+ }
+ return nwritten, 0
+}
+
+// pathCreateDirectory is the WASI function named PathCreateDirectoryName which
+// creates a directory.
+//
+// # Parameters
+//
+// - fd: file descriptor of a directory that `path` is relative to
+// - path: offset in api.Memory to read the path string from
+// - pathLen: length of `path`
+//
+// # Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.ENOENT: `path` does not exist.
+// - sys.ENOTDIR: `path` is a file
+//
+// # Notes
+// - This is similar to mkdirat in POSIX.
+// See https://linux.die.net/man/2/mkdirat
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno
+var pathCreateDirectory = newHostFunc(
+ wasip1.PathCreateDirectoryName, pathCreateDirectoryFn,
+ []wasm.ValueType{i32, i32, i32},
+ "fd", "path", "path_len",
+)
+
+func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ path := uint32(params[1])
+ pathLen := uint32(params[2])
+
+ preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ if errno = preopen.Mkdir(pathName, 0o700); errno != 0 {
+ return errno
+ }
+
+ return 0
+}
+
+// pathFilestatGet is the WASI function named PathFilestatGetName which
+// returns the stat attributes of a file or directory.
+//
+// # Parameters
+//
+// - fd: file descriptor of the folder to look in for the path
+// - flags: flags determining the method of how paths are resolved
+// - path: path under fd to get the filestat attributes data for
+// - path_len: length of the path that was given
+// - resultFilestat: offset to write the result filestat data
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.ENOTDIR: `fd` points to a file not a directory
+// - sys.EIO: could not stat `fd` on filesystem
+// - sys.EINVAL: the path contained "../"
+// - sys.ENAMETOOLONG: `path` + `path_len` is out of memory
+// - sys.EFAULT: `resultFilestat` points to an offset out of memory
+// - sys.ENOENT: could not find the path
+//
+// The rest of this implementation matches that of fdFilestatGet, so is not
+// repeated here.
+//
+// Note: This is similar to `fstatat` in POSIX.
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat
+// and https://linux.die.net/man/2/fstatat
+var pathFilestatGet = newHostFunc(
+ wasip1.PathFilestatGetName, pathFilestatGetFn,
+ []api.ValueType{i32, i32, i32, i32, i32},
+ "fd", "flags", "path", "path_len", "result.filestat",
+)
+
+func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ flags := uint16(params[1])
+ path := uint32(params[2])
+ pathLen := uint32(params[3])
+
+ preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ // Stat the file without allocating a file descriptor.
+ var st sysapi.Stat_t
+
+ if (flags & wasip1.LOOKUP_SYMLINK_FOLLOW) == 0 {
+ st, errno = preopen.Lstat(pathName)
+ } else {
+ st, errno = preopen.Stat(pathName)
+ }
+ if errno != 0 {
+ return errno
+ }
+
+ // Write the stat result to memory
+ resultBuf := uint32(params[4])
+ buf, ok := mod.Memory().Read(resultBuf, 64)
+ if !ok {
+ return experimentalsys.EFAULT
+ }
+
+ filetype := getWasiFiletype(st.Mode)
+ return writeFilestat(buf, &st, filetype)
+}
+
+// pathFilestatSetTimes is the WASI function named PathFilestatSetTimesName
+// which adjusts the timestamps of a file or directory.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
+var pathFilestatSetTimes = newHostFunc(
+ wasip1.PathFilestatSetTimesName, pathFilestatSetTimesFn,
+ []wasm.ValueType{i32, i32, i32, i32, i64, i64, i32},
+ "fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags",
+)
+
+func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fd := int32(params[0])
+ flags := uint16(params[1])
+ path := uint32(params[2])
+ pathLen := uint32(params[3])
+ atim := int64(params[4])
+ mtim := int64(params[5])
+ fstFlags := uint16(params[6])
+
+ sys := mod.(*wasm.ModuleInstance).Sys
+ fsc := sys.FS()
+
+ atim, mtim, errno := toTimes(sys.WalltimeNanos, atim, mtim, fstFlags)
+ if errno != 0 {
+ return errno
+ }
+
+ preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ symlinkFollow := flags&wasip1.LOOKUP_SYMLINK_FOLLOW != 0
+ if symlinkFollow {
+ return preopen.Utimens(pathName, atim, mtim)
+ }
+ // Otherwise, we need to emulate don't follow by opening the file by path.
+ if f, errno := preopen.OpenFile(pathName, experimentalsys.O_WRONLY, 0); errno != 0 {
+ return errno
+ } else {
+ defer f.Close()
+ return f.Utimens(atim, mtim)
+ }
+}
+
+// pathLink is the WASI function named PathLinkName which adjusts the
+// timestamps of a file or directory.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link
+var pathLink = newHostFunc(
+ wasip1.PathLinkName, pathLinkFn,
+ []wasm.ValueType{i32, i32, i32, i32, i32, i32, i32},
+ "old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
+)
+
+func pathLinkFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ oldFD := int32(params[0])
+ // TODO: use old_flags?
+ _ = uint32(params[1])
+ oldPath := uint32(params[2])
+ oldPathLen := uint32(params[3])
+
+ oldFS, oldName, errno := atPath(fsc, mem, oldFD, oldPath, oldPathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ newFD := int32(params[4])
+ newPath := uint32(params[5])
+ newPathLen := uint32(params[6])
+
+ newFS, newName, errno := atPath(fsc, mem, newFD, newPath, newPathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ if oldFS != newFS { // TODO: handle link across filesystems
+ return experimentalsys.ENOSYS
+ }
+
+ return oldFS.Link(oldName, newName)
+}
+
+// pathOpen is the WASI function named PathOpenName which opens a file or
+// directory. This returns sys.EBADF if the fd is invalid.
+//
+// # Parameters
+//
+// - fd: file descriptor of a directory that `path` is relative to
+// - dirflags: flags to indicate how to resolve `path`
+// - path: offset in api.Memory to read the path string from
+// - pathLen: length of `path`
+// - oFlags: open flags to indicate the method by which to open the file
+// - fsRightsBase: interpret RIGHT_FD_WRITE to set O_RDWR
+// - fsRightsInheriting: ignored as rights were removed from WASI.
+// created file descriptor for `path`
+// - fdFlags: file descriptor flags
+// - resultOpenedFD: offset in api.Memory to write the newly created file
+// descriptor to.
+// - The result FD value is guaranteed to be less than 2**31
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.EFAULT: `resultOpenedFD` points to an offset out of memory
+// - sys.ENOENT: `path` does not exist.
+// - sys.EEXIST: `path` exists, while `oFlags` requires that it must not.
+// - sys.ENOTDIR: `path` is not a directory, while `oFlags` requires it.
+// - sys.EIO: a file system error
+//
+// For example, this function needs to first read `path` to determine the file
+// to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero",
+// pathOpen reads the path from api.Memory:
+//
+// pathLen
+// +------------------------+
+// | |
+// []byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... }
+// path --^
+//
+// Then, if parameters resultOpenedFD = 8, and this function opened a new file
+// descriptor 5 with the given flags, this function writes the below to
+// api.Memory:
+//
+// uint32le
+// +--------+
+// | |
+// []byte{ 0..6, ?, 5, 0, 0, 0, ?}
+// resultOpenedFD --^
+//
+// # Notes
+// - This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat
+// - The returned file descriptor is not guaranteed to be the lowest-number
+//
+// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
+var pathOpen = newHostFunc(
+ wasip1.PathOpenName, pathOpenFn,
+ []api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
+ "fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd",
+)
+
+func pathOpenFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ preopenFD := int32(params[0])
+
+ // TODO: dirflags is a lookupflags, and it only has one bit: symlink_follow
+ // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags
+ dirflags := uint16(params[1])
+
+ path := uint32(params[2])
+ pathLen := uint32(params[3])
+
+ oflags := uint16(params[4])
+
+ rights := uint32(params[5])
+ // inherited rights aren't used
+ _ = params[6]
+
+ fdflags := uint16(params[7])
+ resultOpenedFD := uint32(params[8])
+
+ preopen, pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ if pathLen == 0 {
+ return experimentalsys.EINVAL
+ }
+
+ fileOpenFlags := openFlags(dirflags, oflags, fdflags, rights)
+ isDir := fileOpenFlags&experimentalsys.O_DIRECTORY != 0
+
+ if isDir && oflags&wasip1.O_CREAT != 0 {
+ return experimentalsys.EINVAL // use pathCreateDirectory!
+ }
+
+ newFD, errno := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600)
+ if errno != 0 {
+ return errno
+ }
+
+ // Check any flags that require the file to evaluate.
+ if isDir {
+ if f, ok := fsc.LookupFile(newFD); !ok {
+ return experimentalsys.EBADF // unexpected
+ } else if isDir, errno := f.File.IsDir(); errno != 0 {
+ _ = fsc.CloseFile(newFD)
+ return errno
+ } else if !isDir {
+ _ = fsc.CloseFile(newFD)
+ return experimentalsys.ENOTDIR
+ }
+ }
+
+ if !mod.Memory().WriteUint32Le(resultOpenedFD, uint32(newFD)) {
+ _ = fsc.CloseFile(newFD)
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+// atPath returns the pre-open specific path after verifying it is a directory.
+//
+// # Notes
+//
+// Languages including Zig and Rust use only pre-opens for the FD because
+// wasi-libc `__wasilibc_find_relpath` will only return a preopen. That said,
+// our wasi.c example shows other languages act differently and can use a non
+// pre-opened file descriptor.
+//
+// We don't handle `AT_FDCWD`, as that's resolved in the compiler. There's no
+// working directory function in WASI, so most assume CWD is "/". Notably, Zig
+// has different behavior which assumes it is whatever the first pre-open name
+// is.
+//
+// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c
+// See https://linux.die.net/man/2/openat
+func atPath(fsc *sys.FSContext, mem api.Memory, fd int32, p, pathLen uint32) (experimentalsys.FS, string, experimentalsys.Errno) {
+ b, ok := mem.Read(p, pathLen)
+ if !ok {
+ return nil, "", experimentalsys.EFAULT
+ }
+ pathName := string(b)
+
+ // interesting_paths wants us to break on trailing slash if the input ends
+ // up a file, not a directory!
+ hasTrailingSlash := strings.HasSuffix(pathName, "/")
+
+ // interesting_paths includes paths that include relative links but end up
+ // not escaping
+ pathName = path.Clean(pathName)
+
+ // interesting_paths wants to break on root paths or anything that escapes.
+ // This part is the same as fs.FS.Open()
+ if !fs.ValidPath(pathName) {
+ return nil, "", experimentalsys.EPERM
+ }
+
+ // add the trailing slash back
+ if hasTrailingSlash {
+ pathName = pathName + "/"
+ }
+
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return nil, "", experimentalsys.EBADF // closed or invalid
+ } else if isDir, errno := f.File.IsDir(); errno != 0 {
+ return nil, "", errno
+ } else if !isDir {
+ return nil, "", experimentalsys.ENOTDIR
+ } else if f.IsPreopen { // don't append the pre-open name
+ return f.FS, pathName, 0
+ } else {
+ // Join via concat to avoid name conflict on path.Join
+ return f.FS, f.Name + "/" + pathName, 0
+ }
+}
+
+func preopenPath(fsc *sys.FSContext, fd int32) (string, experimentalsys.Errno) {
+ if f, ok := fsc.LookupFile(fd); !ok {
+ return "", experimentalsys.EBADF // closed
+ } else if !f.IsPreopen {
+ return "", experimentalsys.EBADF
+ } else if isDir, errno := f.File.IsDir(); errno != 0 || !isDir {
+ // In wasip1, only directories can be returned by fd_prestat_get as
+ // there are no prestat types defined for files or sockets.
+ return "", errno
+ } else {
+ return f.Name, 0
+ }
+}
+
+func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags experimentalsys.Oflag) {
+ if dirflags&wasip1.LOOKUP_SYMLINK_FOLLOW == 0 {
+ openFlags |= experimentalsys.O_NOFOLLOW
+ }
+ if oflags&wasip1.O_DIRECTORY != 0 {
+ openFlags |= experimentalsys.O_DIRECTORY
+ } else if oflags&wasip1.O_EXCL != 0 {
+ openFlags |= experimentalsys.O_EXCL
+ }
+ // Because we don't implement rights, we partially rely on the open flags
+ // to determine the mode in which the file will be opened. This will create
+ // divergent behavior compared to WASI runtimes which have a more strict
+ // interpretation of the WASI capabilities model; for example, a program
+ // which sets O_CREAT but does not give read or write permissions will
+ // successfully create a file when running with wazero, but might get a
+ // permission denied error on other runtimes.
+ defaultMode := experimentalsys.O_RDONLY
+ if oflags&wasip1.O_TRUNC != 0 {
+ openFlags |= experimentalsys.O_TRUNC
+ defaultMode = experimentalsys.O_RDWR
+ }
+ if oflags&wasip1.O_CREAT != 0 {
+ openFlags |= experimentalsys.O_CREAT
+ defaultMode = experimentalsys.O_RDWR
+ }
+ if fdflags&wasip1.FD_NONBLOCK != 0 {
+ openFlags |= experimentalsys.O_NONBLOCK
+ }
+ if fdflags&wasip1.FD_APPEND != 0 {
+ openFlags |= experimentalsys.O_APPEND
+ defaultMode = experimentalsys.O_RDWR
+ }
+ if fdflags&wasip1.FD_DSYNC != 0 {
+ openFlags |= experimentalsys.O_DSYNC
+ }
+ if fdflags&wasip1.FD_RSYNC != 0 {
+ openFlags |= experimentalsys.O_RSYNC
+ }
+ if fdflags&wasip1.FD_SYNC != 0 {
+ openFlags |= experimentalsys.O_SYNC
+ }
+
+ // Since rights were discontinued in wasi, we only interpret RIGHT_FD_WRITE
+ // because it is the only way to know that we need to set write permissions
+ // on a file if the application did not pass any of O_CREAT, O_APPEND, nor
+ // O_TRUNC.
+ const r = wasip1.RIGHT_FD_READ
+ const w = wasip1.RIGHT_FD_WRITE
+ const rw = r | w
+ switch {
+ case (rights & rw) == rw:
+ openFlags |= experimentalsys.O_RDWR
+ case (rights & w) == w:
+ openFlags |= experimentalsys.O_WRONLY
+ case (rights & r) == r:
+ openFlags |= experimentalsys.O_RDONLY
+ default:
+ openFlags |= defaultMode
+ }
+ return
+}
+
+// pathReadlink is the WASI function named PathReadlinkName that reads the
+// contents of a symbolic link.
+//
+// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size
+var pathReadlink = newHostFunc(
+ wasip1.PathReadlinkName, pathReadlinkFn,
+ []wasm.ValueType{i32, i32, i32, i32, i32, i32},
+ "fd", "path", "path_len", "buf", "buf_len", "result.bufused",
+)
+
+func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ path := uint32(params[1])
+ pathLen := uint32(params[2])
+ buf := uint32(params[3])
+ bufLen := uint32(params[4])
+ resultBufused := uint32(params[5])
+
+ if pathLen == 0 || bufLen == 0 {
+ return experimentalsys.EINVAL
+ }
+
+ mem := mod.Memory()
+ preopen, p, errno := atPath(fsc, mem, fd, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ dst, errno := preopen.Readlink(p)
+ if errno != 0 {
+ return errno
+ }
+
+ if len(dst) > int(bufLen) {
+ return experimentalsys.ERANGE
+ }
+
+ if ok := mem.WriteString(buf, dst); !ok {
+ return experimentalsys.EFAULT
+ }
+
+ if !mem.WriteUint32Le(resultBufused, uint32(len(dst))) {
+ return experimentalsys.EFAULT
+ }
+ return 0
+}
+
+// pathRemoveDirectory is the WASI function named PathRemoveDirectoryName which
+// removes a directory.
+//
+// # Parameters
+//
+// - fd: file descriptor of a directory that `path` is relative to
+// - path: offset in api.Memory to read the path string from
+// - pathLen: length of `path`
+//
+// # Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.ENOENT: `path` does not exist.
+// - sys.ENOTEMPTY: `path` is not empty
+// - sys.ENOTDIR: `path` is a file
+//
+// # Notes
+// - This is similar to unlinkat with AT_REMOVEDIR in POSIX.
+// See https://linux.die.net/man/2/unlinkat
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno
+var pathRemoveDirectory = newHostFunc(
+ wasip1.PathRemoveDirectoryName, pathRemoveDirectoryFn,
+ []wasm.ValueType{i32, i32, i32},
+ "fd", "path", "path_len",
+)
+
+func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ path := uint32(params[1])
+ pathLen := uint32(params[2])
+
+ preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ return preopen.Rmdir(pathName)
+}
+
+// pathRename is the WASI function named PathRenameName which renames a file or
+// directory.
+//
+// # Parameters
+//
+// - fd: file descriptor of a directory that `old_path` is relative to
+// - old_path: offset in api.Memory to read the old path string from
+// - old_path_len: length of `old_path`
+// - new_fd: file descriptor of a directory that `new_path` is relative to
+// - new_path: offset in api.Memory to read the new path string from
+// - new_path_len: length of `new_path`
+//
+// # Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` or `new_fd` are invalid
+// - sys.ENOENT: `old_path` does not exist.
+// - sys.ENOTDIR: `old` is a directory and `new` exists, but is a file.
+// - sys.EISDIR: `old` is a file and `new` exists, but is a directory.
+//
+// # Notes
+// - This is similar to unlinkat in POSIX.
+// See https://linux.die.net/man/2/renameat
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_renamefd-fd-old_path-string-new_fd-fd-new_path-string---errno
+var pathRename = newHostFunc(
+ wasip1.PathRenameName, pathRenameFn,
+ []wasm.ValueType{i32, i32, i32, i32, i32, i32},
+ "fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
+)
+
+func pathRenameFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ oldPath := uint32(params[1])
+ oldPathLen := uint32(params[2])
+
+ newFD := int32(params[3])
+ newPath := uint32(params[4])
+ newPathLen := uint32(params[5])
+
+ oldFS, oldPathName, errno := atPath(fsc, mod.Memory(), fd, oldPath, oldPathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ newFS, newPathName, errno := atPath(fsc, mod.Memory(), newFD, newPath, newPathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ if oldFS != newFS { // TODO: handle renames across filesystems
+ return experimentalsys.ENOSYS
+ }
+
+ return oldFS.Rename(oldPathName, newPathName)
+}
+
+// pathSymlink is the WASI function named PathSymlinkName which creates a
+// symbolic link.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink
+var pathSymlink = newHostFunc(
+ wasip1.PathSymlinkName, pathSymlinkFn,
+ []wasm.ValueType{i32, i32, i32, i32, i32},
+ "old_path", "old_path_len", "fd", "new_path", "new_path_len",
+)
+
+func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ oldPath := uint32(params[0])
+ oldPathLen := uint32(params[1])
+ fd := int32(params[2])
+ newPath := uint32(params[3])
+ newPathLen := uint32(params[4])
+
+ mem := mod.Memory()
+
+ dir, ok := fsc.LookupFile(fd)
+ if !ok {
+ return experimentalsys.EBADF // closed
+ } else if isDir, errno := dir.File.IsDir(); errno != 0 {
+ return errno
+ } else if !isDir {
+ return experimentalsys.ENOTDIR
+ }
+
+ if oldPathLen == 0 || newPathLen == 0 {
+ return experimentalsys.EINVAL
+ }
+
+ oldPathBuf, ok := mem.Read(oldPath, oldPathLen)
+ if !ok {
+ return experimentalsys.EFAULT
+ }
+
+ _, newPathName, errno := atPath(fsc, mod.Memory(), fd, newPath, newPathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ return dir.FS.Symlink(
+ // Do not join old path since it's only resolved when dereference the link created here.
+ // And the dereference result depends on the opening directory's file descriptor at that point.
+ bufToStr(oldPathBuf),
+ newPathName,
+ )
+}
+
+// bufToStr converts the given byte slice as string unsafely.
+func bufToStr(buf []byte) string {
+ // TODO: use unsafe.String after flooring Go 1.20.
+ return *(*string)(unsafe.Pointer(&buf))
+}
+
+// pathUnlinkFile is the WASI function named PathUnlinkFileName which unlinks a
+// file.
+//
+// # Parameters
+//
+// - fd: file descriptor of a directory that `path` is relative to
+// - path: offset in api.Memory to read the path string from
+// - pathLen: length of `path`
+//
+// # Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EBADF: `fd` is invalid
+// - sys.ENOENT: `path` does not exist.
+// - sys.EISDIR: `path` is a directory
+//
+// # Notes
+// - This is similar to unlinkat without AT_REMOVEDIR in POSIX.
+// See https://linux.die.net/man/2/unlinkat
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno
+var pathUnlinkFile = newHostFunc(
+ wasip1.PathUnlinkFileName, pathUnlinkFileFn,
+ []wasm.ValueType{i32, i32, i32},
+ "fd", "path", "path_len",
+)
+
+func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ path := uint32(params[1])
+ pathLen := uint32(params[2])
+
+ preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
+ if errno != 0 {
+ return errno
+ }
+
+ return preopen.Unlink(pathName)
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go
new file mode 100644
index 000000000..d09f30245
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go
@@ -0,0 +1,239 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+ "time"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/fsapi"
+ internalsys "github.com/tetratelabs/wazero/internal/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// pollOneoff is the WASI function named PollOneoffName that concurrently
+// polls for the occurrence of a set of events.
+//
+// # Parameters
+//
+// - in: pointer to the subscriptions (48 bytes each)
+// - out: pointer to the resulting events (32 bytes each)
+// - nsubscriptions: count of subscriptions, zero returns sys.EINVAL.
+// - resultNevents: count of events.
+//
+// Result (Errno)
+//
+// The return value is 0 except the following error conditions:
+// - sys.EINVAL: the parameters are invalid
+// - sys.ENOTSUP: a parameters is valid, but not yet supported.
+// - sys.EFAULT: there is not enough memory to read the subscriptions or
+// write results.
+//
+// # Notes
+//
+// - Since the `out` pointer nests Errno, the result is always 0.
+// - This is similar to `poll` in POSIX.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
+// See https://linux.die.net/man/3/poll
+var pollOneoff = newHostFunc(
+ wasip1.PollOneoffName, pollOneoffFn,
+ []api.ValueType{i32, i32, i32, i32},
+ "in", "out", "nsubscriptions", "result.nevents",
+)
+
+type event struct {
+ eventType byte
+ userData []byte
+ errno wasip1.Errno
+}
+
+func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ in := uint32(params[0])
+ out := uint32(params[1])
+ nsubscriptions := uint32(params[2])
+ resultNevents := uint32(params[3])
+
+ if nsubscriptions == 0 {
+ return sys.EINVAL
+ }
+
+ mem := mod.Memory()
+
+ // Ensure capacity prior to the read loop to reduce error handling.
+ inBuf, ok := mem.Read(in, nsubscriptions*48)
+ if !ok {
+ return sys.EFAULT
+ }
+ outBuf, ok := mem.Read(out, nsubscriptions*32)
+ // zero-out all buffer before writing
+ for i := range outBuf {
+ outBuf[i] = 0
+ }
+
+ if !ok {
+ return sys.EFAULT
+ }
+
+ // Eagerly write the number of events which will equal subscriptions unless
+ // there's a fault in parsing (not processing).
+ if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
+ return sys.EFAULT
+ }
+
+ // Loop through all subscriptions and write their output.
+
+ // Extract FS context, used in the body of the for loop for FS access.
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+ // Slice of events that are processed out of the loop (blocking stdin subscribers).
+ var blockingStdinSubs []*event
+ // The timeout is initialized at max Duration, the loop will find the minimum.
+ var timeout time.Duration = 1<<63 - 1
+ // Count of all the subscriptions that have been already written back to outBuf.
+ // nevents*32 returns at all times the offset where the next event should be written:
+ // this way we ensure that there are no gaps between records.
+ nevents := uint32(0)
+
+ // Layout is subscription_u: Union
+ // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u
+ for i := uint32(0); i < nsubscriptions; i++ {
+ inOffset := i * 48
+ outOffset := nevents * 32
+
+ eventType := inBuf[inOffset+8] // +8 past userdata
+ // +8 past userdata +8 contents_offset
+ argBuf := inBuf[inOffset+8+8:]
+ userData := inBuf[inOffset : inOffset+8]
+
+ evt := &event{
+ eventType: eventType,
+ userData: userData,
+ errno: wasip1.ErrnoSuccess,
+ }
+
+ switch eventType {
+ case wasip1.EventTypeClock: // handle later
+ newTimeout, err := processClockEvent(argBuf)
+ if err != 0 {
+ return err
+ }
+ // Min timeout.
+ if newTimeout < timeout {
+ timeout = newTimeout
+ }
+ // Ack the clock event to the outBuf.
+ writeEvent(outBuf[outOffset:], evt)
+ nevents++
+ case wasip1.EventTypeFdRead:
+ fd := int32(le.Uint32(argBuf))
+ if fd < 0 {
+ return sys.EBADF
+ }
+ if file, ok := fsc.LookupFile(fd); !ok {
+ evt.errno = wasip1.ErrnoBadf
+ writeEvent(outBuf[outOffset:], evt)
+ nevents++
+ } else if fd != internalsys.FdStdin && file.File.IsNonblock() {
+ writeEvent(outBuf[outOffset:], evt)
+ nevents++
+ } else {
+ // if the fd is Stdin, and it is in blocking mode,
+ // do not ack yet, append to a slice for delayed evaluation.
+ blockingStdinSubs = append(blockingStdinSubs, evt)
+ }
+ case wasip1.EventTypeFdWrite:
+ fd := int32(le.Uint32(argBuf))
+ if fd < 0 {
+ return sys.EBADF
+ }
+ if _, ok := fsc.LookupFile(fd); ok {
+ evt.errno = wasip1.ErrnoNotsup
+ } else {
+ evt.errno = wasip1.ErrnoBadf
+ }
+ nevents++
+ writeEvent(outBuf[outOffset:], evt)
+ default:
+ return sys.EINVAL
+ }
+ }
+
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ if nevents == nsubscriptions {
+ // We already wrote back all the results. We already wrote this number
+ // earlier to offset `resultNevents`.
+ // We only need to observe the timeout (nonzero if there are clock subscriptions)
+ // and return.
+ if timeout > 0 {
+ sysCtx.Nanosleep(int64(timeout))
+ }
+ return 0
+ }
+
+ // If there are blocking stdin subscribers, check for data with given timeout.
+ stdin, ok := fsc.LookupFile(internalsys.FdStdin)
+ if !ok {
+ return sys.EBADF
+ }
+ // Wait for the timeout to expire, or for some data to become available on Stdin.
+
+ if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 {
+ return errno
+ } else if stdinReady {
+ // stdin has data ready to for reading, write back all the events
+ for i := range blockingStdinSubs {
+ evt := blockingStdinSubs[i]
+ evt.errno = 0
+ writeEvent(outBuf[nevents*32:], evt)
+ nevents++
+ }
+ }
+
+ if nevents != nsubscriptions {
+ if !mod.Memory().WriteUint32Le(resultNevents, nevents) {
+ return sys.EFAULT
+ }
+ }
+
+ return 0
+}
+
+// processClockEvent supports only relative name events, as that's what's used
+// to implement sleep in various compilers including Rust, Zig and TinyGo.
+func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) {
+ _ /* ID */ = le.Uint32(inBuf[0:8]) // See below
+ timeout := le.Uint64(inBuf[8:16]) // nanos if relative
+ _ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
+ flags := le.Uint16(inBuf[24:32])
+
+ var err sys.Errno
+ // subclockflags has only one flag defined: subscription_clock_abstime
+ switch flags {
+ case 0: // relative time
+ case 1: // subscription_clock_abstime
+ err = sys.ENOTSUP
+ default: // subclockflags has only one flag defined.
+ err = sys.EINVAL
+ }
+
+ if err != 0 {
+ return 0, err
+ } else {
+ // https://linux.die.net/man/3/clock_settime says relative timers are
+ // unaffected. Since this function only supports relative timeout, we can
+ // skip name ID validation and use a single sleep function.
+
+ return time.Duration(timeout), 0
+ }
+}
+
+// writeEvent writes the event corresponding to the processed subscription.
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
+func writeEvent(outBuf []byte, evt *event) {
+ copy(outBuf, evt.userData) // userdata
+ outBuf[8] = byte(evt.errno) // uint16, but safe as < 255
+ outBuf[9] = 0
+ le.PutUint32(outBuf[10:], uint32(evt.eventType))
+ // TODO: When FD events are supported, write outOffset+16
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go
new file mode 100644
index 000000000..cb0ab487c
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go
@@ -0,0 +1,44 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+ "github.com/tetratelabs/wazero/sys"
+)
+
+// procExit is the WASI function named ProcExitName that terminates the
+// execution of the module with an exit code. The only successful exit code is
+// zero.
+//
+// # Parameters
+//
+// - exitCode: exit code.
+//
+// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
+var procExit = &wasm.HostFunc{
+ ExportName: wasip1.ProcExitName,
+ Name: wasip1.ProcExitName,
+ ParamTypes: []api.ValueType{i32},
+ ParamNames: []string{"rval"},
+ Code: wasm.Code{GoFunc: api.GoModuleFunc(procExitFn)},
+}
+
+func procExitFn(ctx context.Context, mod api.Module, params []uint64) {
+ exitCode := uint32(params[0])
+
+ // Ensure other callers see the exit code.
+ _ = mod.CloseWithExitCode(ctx, exitCode)
+
+ // Prevent any code from executing after this function. For example, LLVM
+ // inserts unreachable instructions after calls to exit.
+ // See: https://github.com/emscripten-core/emscripten/issues/12322
+ panic(sys.NewExitError(exitCode))
+}
+
+// procRaise is stubbed and will never be supported, as it was removed.
+//
+// See https://github.com/WebAssembly/WASI/pull/136
+var procRaise = stubFunction(wasip1.ProcRaiseName, []api.ValueType{i32}, "sig")
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go
new file mode 100644
index 000000000..e4d7ccee1
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go
@@ -0,0 +1,55 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+ "io"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// randomGet is the WASI function named RandomGetName which writes random
+// data to a buffer.
+//
+// # Parameters
+//
+// - buf: api.Memory offset to write random values
+// - bufLen: size of random data in bytes
+//
+// Result (Errno)
+//
+// The return value is ErrnoSuccess except the following error conditions:
+// - sys.EFAULT: `buf` or `bufLen` point to an offset out of memory
+// - sys.EIO: a file system error
+//
+// For example, if underlying random source was seeded like
+// `rand.NewSource(42)`, we expect api.Memory to contain:
+//
+// bufLen (5)
+// +--------------------------+
+// | |
+// []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?}
+// buf --^
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
+var randomGet = newHostFunc(wasip1.RandomGetName, randomGetFn, []api.ValueType{i32, i32}, "buf", "buf_len")
+
+func randomGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ randSource := sysCtx.RandSource()
+ buf, bufLen := uint32(params[0]), uint32(params[1])
+
+ randomBytes, ok := mod.Memory().Read(buf, bufLen)
+ if !ok { // out-of-range
+ return sys.EFAULT
+ }
+
+ // We can ignore the returned n as it only != byteCount on error
+ if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil {
+ return sys.EIO
+ }
+
+ return 0
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go
new file mode 100644
index 000000000..86748e6d6
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go
@@ -0,0 +1,22 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// schedYield is the WASI function named SchedYieldName which temporarily
+// yields execution of the calling thread.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno
+var schedYield = newHostFunc(wasip1.SchedYieldName, schedYieldFn, nil)
+
+func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) sys.Errno {
+ sysCtx := mod.(*wasm.ModuleInstance).Sys
+ sysCtx.Osyield()
+ return 0
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go
new file mode 100644
index 000000000..756c0d391
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go
@@ -0,0 +1,188 @@
+package wasi_snapshot_preview1
+
+import (
+ "context"
+
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ socketapi "github.com/tetratelabs/wazero/internal/sock"
+ "github.com/tetratelabs/wazero/internal/sysfs"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// sockAccept is the WASI function named SockAcceptName which accepts a new
+// incoming connection.
+//
+// See: https://github.com/WebAssembly/WASI/blob/0ba0c5e2e37625ca5a6d3e4255a998dfaa3efc52/phases/snapshot/docs.md#sock_accept
+// and https://github.com/WebAssembly/WASI/pull/458
+var sockAccept = newHostFunc(
+ wasip1.SockAcceptName,
+ sockAcceptFn,
+ []wasm.ValueType{i32, i32, i32},
+ "fd", "flags", "result.fd",
+)
+
+func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno sys.Errno) {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ flags := uint32(params[1])
+ resultFd := uint32(params[2])
+ nonblock := flags&uint32(wasip1.FD_NONBLOCK) != 0
+
+ var connFD int32
+ if connFD, errno = fsc.SockAccept(fd, nonblock); errno == 0 {
+ mem.WriteUint32Le(resultFd, uint32(connFD))
+ }
+ return
+}
+
+// sockRecv is the WASI function named SockRecvName which receives a
+// message from a socket.
+//
+// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_recvfd-fd-ri_data-iovec_array-ri_flags-riflags---errno-size-roflags
+var sockRecv = newHostFunc(
+ wasip1.SockRecvName,
+ sockRecvFn,
+ []wasm.ValueType{i32, i32, i32, i32, i32, i32},
+ "fd", "ri_data", "ri_data_len", "ri_flags", "result.ro_datalen", "result.ro_flags",
+)
+
+func sockRecvFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ riData := uint32(params[1])
+ riDataCount := uint32(params[2])
+ riFlags := uint8(params[3])
+ resultRoDatalen := uint32(params[4])
+ resultRoFlags := uint32(params[5])
+
+ var conn socketapi.TCPConn
+ if e, ok := fsc.LookupFile(fd); !ok {
+ return sys.EBADF // Not open
+ } else if conn, ok = e.File.(socketapi.TCPConn); !ok {
+ return sys.EBADF // Not a conn
+ }
+
+ if riFlags & ^(wasip1.RI_RECV_PEEK|wasip1.RI_RECV_WAITALL) != 0 {
+ return sys.ENOTSUP
+ }
+
+ if riFlags&wasip1.RI_RECV_PEEK != 0 {
+ // Each record in riData is of the form:
+ // type iovec struct { buf *uint8; bufLen uint32 }
+ // This means that the first `uint32` is a `buf *uint8`.
+ firstIovecBufAddr, ok := mem.ReadUint32Le(riData)
+ if !ok {
+ return sys.EINVAL
+ }
+ // Read bufLen
+ firstIovecBufLen, ok := mem.ReadUint32Le(riData + 4)
+ if !ok {
+ return sys.EINVAL
+ }
+ firstIovecBuf, ok := mem.Read(firstIovecBufAddr, firstIovecBufLen)
+ if !ok {
+ return sys.EINVAL
+ }
+ n, err := conn.Recvfrom(firstIovecBuf, sysfs.MSG_PEEK)
+ if err != 0 {
+ return err
+ }
+ mem.WriteUint32Le(resultRoDatalen, uint32(n))
+ mem.WriteUint16Le(resultRoFlags, 0)
+ return 0
+ }
+
+ // If riFlags&wasip1.RECV_WAITALL != 0 then we should
+ // do a blocking operation until all data has been retrieved;
+ // otherwise we are able to return earlier.
+ // For simplicity, we currently wait all regardless the flag.
+ bufSize, errno := readv(mem, riData, riDataCount, conn.Read)
+ if errno != 0 {
+ return errno
+ }
+ mem.WriteUint32Le(resultRoDatalen, bufSize)
+ mem.WriteUint16Le(resultRoFlags, 0)
+ return 0
+}
+
+// sockSend is the WASI function named SockSendName which sends a message
+// on a socket.
+//
+// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_sendfd-fd-si_data-ciovec_array-si_flags-siflags---errno-size
+var sockSend = newHostFunc(
+ wasip1.SockSendName,
+ sockSendFn,
+ []wasm.ValueType{i32, i32, i32, i32, i32},
+ "fd", "si_data", "si_data_len", "si_flags", "result.so_datalen",
+)
+
+func sockSendFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ mem := mod.Memory()
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ siData := uint32(params[1])
+ siDataCount := uint32(params[2])
+ siFlags := uint32(params[3])
+ resultSoDatalen := uint32(params[4])
+
+ if siFlags != 0 {
+ return sys.ENOTSUP
+ }
+
+ var conn socketapi.TCPConn
+ if e, ok := fsc.LookupFile(fd); !ok {
+ return sys.EBADF // Not open
+ } else if conn, ok = e.File.(socketapi.TCPConn); !ok {
+ return sys.EBADF // Not a conn
+ }
+
+ bufSize, errno := writev(mem, siData, siDataCount, conn.Write)
+ if errno != 0 {
+ return errno
+ }
+ mem.WriteUint32Le(resultSoDatalen, bufSize)
+ return 0
+}
+
+// sockShutdown is the WASI function named SockShutdownName which shuts
+// down socket send and receive channels.
+//
+// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno
+var sockShutdown = newHostFunc(wasip1.SockShutdownName, sockShutdownFn, []wasm.ValueType{i32, i32}, "fd", "how")
+
+func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
+ fsc := mod.(*wasm.ModuleInstance).Sys.FS()
+
+ fd := int32(params[0])
+ how := uint8(params[1])
+
+ var conn socketapi.TCPConn
+ if e, ok := fsc.LookupFile(fd); !ok {
+ return sys.EBADF // Not open
+ } else if conn, ok = e.File.(socketapi.TCPConn); !ok {
+ return sys.EBADF // Not a conn
+ }
+
+ sysHow := 0
+
+ switch how {
+ case wasip1.SD_RD | wasip1.SD_WR:
+ sysHow = socketapi.SHUT_RD | socketapi.SHUT_WR
+ case wasip1.SD_RD:
+ sysHow = socketapi.SHUT_RD
+ case wasip1.SD_WR:
+ sysHow = socketapi.SHUT_WR
+ default:
+ return sys.EINVAL
+ }
+
+ // TODO: Map this instead of relying on syscall symbols.
+ return conn.Shutdown(sysHow)
+}
diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go
new file mode 100644
index 000000000..4ef41d501
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go
@@ -0,0 +1,314 @@
+// Package wasi_snapshot_preview1 contains Go-defined functions to access
+// system calls, such as opening a file, similar to Go's x/sys package. These
+// are accessible from WebAssembly-defined functions via importing ModuleName.
+// All WASI functions return a single Errno result: ErrnoSuccess on success.
+//
+// e.g. Call Instantiate before instantiating any wasm binary that imports
+// "wasi_snapshot_preview1", Otherwise, it will error due to missing imports.
+//
+// ctx := context.Background()
+// r := wazero.NewRuntime(ctx)
+// defer r.Close(ctx) // This closes everything this Runtime created.
+//
+// wasi_snapshot_preview1.MustInstantiate(ctx, r)
+// mod, _ := r.Instantiate(ctx, wasm)
+//
+// See https://github.com/WebAssembly/WASI
+package wasi_snapshot_preview1
+
+import (
+ "context"
+ "encoding/binary"
+
+ "github.com/tetratelabs/wazero"
+ "github.com/tetratelabs/wazero/api"
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/wasip1"
+ "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+// ModuleName is the module name WASI functions are exported into.
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
+const ModuleName = wasip1.InternalModuleName
+
+const i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64
+
+var le = binary.LittleEndian
+
+// MustInstantiate calls Instantiate or panics on error.
+//
+// This is a simpler function for those who know the module ModuleName is not
+// already instantiated, and don't need to unload it.
+func MustInstantiate(ctx context.Context, r wazero.Runtime) {
+ if _, err := Instantiate(ctx, r); err != nil {
+ panic(err)
+ }
+}
+
+// Instantiate instantiates the ModuleName module into the runtime.
+//
+// # Notes
+//
+// - Failure cases are documented on wazero.Runtime InstantiateModule.
+// - Closing the wazero.Runtime has the same effect as closing the result.
+func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
+ return NewBuilder(r).Instantiate(ctx)
+}
+
+// Builder configures the ModuleName module for later use via Compile or Instantiate.
+//
+// # Notes
+//
+// - This is an interface for decoupling, not third-party implementations.
+// All implementations are in wazero.
+type Builder interface {
+ // Compile compiles the ModuleName module. Call this before Instantiate.
+ //
+ // Note: This has the same effect as the same function on wazero.HostModuleBuilder.
+ Compile(context.Context) (wazero.CompiledModule, error)
+
+ // Instantiate instantiates the ModuleName module and returns a function to close it.
+ //
+ // Note: This has the same effect as the same function on wazero.HostModuleBuilder.
+ Instantiate(context.Context) (api.Closer, error)
+}
+
+// NewBuilder returns a new Builder.
+func NewBuilder(r wazero.Runtime) Builder {
+ return &builder{r}
+}
+
+type builder struct{ r wazero.Runtime }
+
+// hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
+func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
+ ret := b.r.NewHostModuleBuilder(ModuleName)
+ exportFunctions(ret)
+ return ret
+}
+
+// Compile implements Builder.Compile
+func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
+ return b.hostModuleBuilder().Compile(ctx)
+}
+
+// Instantiate implements Builder.Instantiate
+func (b *builder) Instantiate(ctx context.Context) (api.Closer, error) {
+ return b.hostModuleBuilder().Instantiate(ctx)
+}
+
+// FunctionExporter exports functions into a wazero.HostModuleBuilder.
+//
+// # Notes
+//
+// - This is an interface for decoupling, not third-party implementations.
+// All implementations are in wazero.
+type FunctionExporter interface {
+ ExportFunctions(wazero.HostModuleBuilder)
+}
+
+// NewFunctionExporter returns a new FunctionExporter. This is used for the
+// following two use cases:
+// - Overriding a builtin function with an alternate implementation.
+// - Exporting functions to the module "wasi_unstable" for legacy code.
+//
+// # Example of overriding default behavior
+//
+// // Export the default WASI functions.
+// wasiBuilder := r.NewHostModuleBuilder(ModuleName)
+// wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
+//
+// // Subsequent calls to NewFunctionBuilder override built-in exports.
+// wasiBuilder.NewFunctionBuilder().
+// WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) {
+// // your custom logic
+// }).Export("proc_exit")
+//
+// # Example of using the old module name for WASI
+//
+// // Instantiate the current WASI functions under the wasi_unstable
+// // instead of wasi_snapshot_preview1.
+// wasiBuilder := r.NewHostModuleBuilder("wasi_unstable")
+// wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
+// _, err := wasiBuilder.Instantiate(testCtx, r)
+func NewFunctionExporter() FunctionExporter {
+ return &functionExporter{}
+}
+
+type functionExporter struct{}
+
+// ExportFunctions implements FunctionExporter.ExportFunctions
+func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
+ exportFunctions(builder)
+}
+
+// ## Translation notes
+// ### String
+// WebAssembly 1.0 has no string type, so any string input parameter expands to two uint32 parameters: offset
+// and length.
+//
+// ### iovec_array
+// `iovec_array` is encoded as two uin32le values (i32): offset and count.
+//
+// ### Result
+// Each result besides Errno is always an uint32 parameter. WebAssembly 1.0 can have up to one result,
+// which is already used by Errno. This forces other results to be parameters. A result parameter is a memory
+// offset to write the result to. As memory offsets are uint32, each parameter representing a result is uint32.
+//
+// ### Errno
+// The WASI specification is sometimes ambiguous resulting in some runtimes interpreting the same function ways.
+// Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt
+// about portability, first look at /RATIONALE.md and if needed an issue on
+// https://github.com/WebAssembly/WASI/issues
+//
+// ## Memory
+// In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means api.Memory is always the
+// wasm.Store Memories index zero: `store.Memories[0].Buffer`
+//
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
+// See https://github.com/WebAssembly/WASI/issues/215
+// See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
+
+// exportFunctions adds all go functions that implement wasi.
+// These should be exported in the module named ModuleName.
+func exportFunctions(builder wazero.HostModuleBuilder) {
+ exporter := builder.(wasm.HostFuncExporter)
+
+ // Note: these are ordered per spec for consistency even if the resulting
+ // map can't guarantee that.
+ // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
+ exporter.ExportHostFunc(argsGet)
+ exporter.ExportHostFunc(argsSizesGet)
+ exporter.ExportHostFunc(environGet)
+ exporter.ExportHostFunc(environSizesGet)
+ exporter.ExportHostFunc(clockResGet)
+ exporter.ExportHostFunc(clockTimeGet)
+ exporter.ExportHostFunc(fdAdvise)
+ exporter.ExportHostFunc(fdAllocate)
+ exporter.ExportHostFunc(fdClose)
+ exporter.ExportHostFunc(fdDatasync)
+ exporter.ExportHostFunc(fdFdstatGet)
+ exporter.ExportHostFunc(fdFdstatSetFlags)
+ exporter.ExportHostFunc(fdFdstatSetRights)
+ exporter.ExportHostFunc(fdFilestatGet)
+ exporter.ExportHostFunc(fdFilestatSetSize)
+ exporter.ExportHostFunc(fdFilestatSetTimes)
+ exporter.ExportHostFunc(fdPread)
+ exporter.ExportHostFunc(fdPrestatGet)
+ exporter.ExportHostFunc(fdPrestatDirName)
+ exporter.ExportHostFunc(fdPwrite)
+ exporter.ExportHostFunc(fdRead)
+ exporter.ExportHostFunc(fdReaddir)
+ exporter.ExportHostFunc(fdRenumber)
+ exporter.ExportHostFunc(fdSeek)
+ exporter.ExportHostFunc(fdSync)
+ exporter.ExportHostFunc(fdTell)
+ exporter.ExportHostFunc(fdWrite)
+ exporter.ExportHostFunc(pathCreateDirectory)
+ exporter.ExportHostFunc(pathFilestatGet)
+ exporter.ExportHostFunc(pathFilestatSetTimes)
+ exporter.ExportHostFunc(pathLink)
+ exporter.ExportHostFunc(pathOpen)
+ exporter.ExportHostFunc(pathReadlink)
+ exporter.ExportHostFunc(pathRemoveDirectory)
+ exporter.ExportHostFunc(pathRename)
+ exporter.ExportHostFunc(pathSymlink)
+ exporter.ExportHostFunc(pathUnlinkFile)
+ exporter.ExportHostFunc(pollOneoff)
+ exporter.ExportHostFunc(procExit)
+ exporter.ExportHostFunc(procRaise)
+ exporter.ExportHostFunc(schedYield)
+ exporter.ExportHostFunc(randomGet)
+ exporter.ExportHostFunc(sockAccept)
+ exporter.ExportHostFunc(sockRecv)
+ exporter.ExportHostFunc(sockSend)
+ exporter.ExportHostFunc(sockShutdown)
+}
+
+// writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
+// for args or environ, given a pre-defined bytesLen (which includes NUL
+// terminators).
+func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno {
+ // The caller may not place bytes directly after offsets, so we have to
+ // read them independently.
+ valuesLen := len(values)
+ offsetsLen := uint32(valuesLen * 4) // uint32Le
+ offsetsBuf, ok := mem.Read(offsets, offsetsLen)
+ if !ok {
+ return sys.EFAULT
+ }
+ bytesBuf, ok := mem.Read(bytes, bytesLen)
+ if !ok {
+ return sys.EFAULT
+ }
+
+ // Loop through the values, first writing the location of its data to
+ // offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI]
+ var oI, bI uint32
+ for _, value := range values {
+ // Go can't guarantee inlining as there's not //go:inline directive.
+ // This inlines uint32 little-endian encoding instead.
+ bytesOffset := bytes + bI
+ offsetsBuf[oI] = byte(bytesOffset)
+ offsetsBuf[oI+1] = byte(bytesOffset >> 8)
+ offsetsBuf[oI+2] = byte(bytesOffset >> 16)
+ offsetsBuf[oI+3] = byte(bytesOffset >> 24)
+ oI += 4 // size of uint32 we just wrote
+
+ // Write the next value to memory with a NUL terminator
+ copy(bytesBuf[bI:], value)
+ bI += uint32(len(value))
+ bytesBuf[bI] = 0 // NUL terminator
+ bI++
+ }
+
+ return 0
+}
+
+func newHostFunc(
+ name string,
+ goFunc wasiFunc,
+ paramTypes []api.ValueType,
+ paramNames ...string,
+) *wasm.HostFunc {
+ return &wasm.HostFunc{
+ ExportName: name,
+ Name: name,
+ ParamTypes: paramTypes,
+ ParamNames: paramNames,
+ ResultTypes: []api.ValueType{i32},
+ ResultNames: []string{"errno"},
+ Code: wasm.Code{GoFunc: goFunc},
+ }
+}
+
+// wasiFunc special cases that all WASI functions return a single Errno
+// result. The returned value will be written back to the stack at index zero.
+type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno
+
+// Call implements the same method as documented on api.GoModuleFunction.
+func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
+ // Write the result back onto the stack
+ errno := f(ctx, mod, stack)
+ if errno != 0 {
+ stack[0] = uint64(wasip1.ToErrno(errno))
+ } else { // special case ass ErrnoSuccess is zero
+ stack[0] = 0
+ }
+}
+
+// stubFunction stubs for GrainLang per #271.
+func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc {
+ return &wasm.HostFunc{
+ ExportName: name,
+ Name: name,
+ ParamTypes: paramTypes,
+ ParamNames: paramNames,
+ ResultTypes: []api.ValueType{i32},
+ ResultNames: []string{"errno"},
+ Code: wasm.Code{
+ GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }),
+ },
+ }
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go
new file mode 100644
index 000000000..212d3b2de
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go
@@ -0,0 +1,6 @@
+package wasip1
+
+const (
+ ArgsGetName = "args_get"
+ ArgsSizesGetName = "args_sizes_get"
+)
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go
new file mode 100644
index 000000000..1d1b8c12d
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go
@@ -0,0 +1,16 @@
+package wasip1
+
+const (
+ ClockResGetName = "clock_res_get"
+ ClockTimeGetName = "clock_time_get"
+)
+
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clockid-enumu32
+const (
+ // ClockIDRealtime is the name ID named "realtime" like sys.Walltime
+ ClockIDRealtime = iota
+ // ClockIDMonotonic is the name ID named "monotonic" like sys.Nanotime
+ ClockIDMonotonic
+ // Note: clockIDProcessCputime and clockIDThreadCputime were removed by
+ // WASI maintainers: https://github.com/WebAssembly/wasi-libc/pull/294
+)
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go
new file mode 100644
index 000000000..2b0d59828
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go
@@ -0,0 +1,6 @@
+package wasip1
+
+const (
+ EnvironGetName = "environ_get"
+ EnvironSizesGetName = "environ_sizes_get"
+)
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go
new file mode 100644
index 000000000..028573d2f
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go
@@ -0,0 +1,314 @@
+package wasip1
+
+import (
+ "fmt"
+
+ "github.com/tetratelabs/wazero/experimental/sys"
+)
+
+// Errno is neither uint16 nor an alias for parity with wasm.ValueType.
+type Errno = uint32
+
+// ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is
+// not an error. e.g. Errno2big -> "E2BIG"
+func ErrnoName(errno uint32) string {
+ if int(errno) < len(errnoToString) {
+ return errnoToString[errno]
+ }
+ return fmt.Sprintf("errno(%d)", errno)
+}
+
+// Note: Below prefers POSIX symbol names over WASI ones, even if the docs are from WASI.
+// See https://linux.die.net/man/3/errno
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#variants-1
+const (
+ // ErrnoSuccess No error occurred. System call completed successfully.
+ ErrnoSuccess Errno = iota
+ // Errno2big Argument list too long.
+ Errno2big
+ // ErrnoAcces Permission denied.
+ ErrnoAcces
+ // ErrnoAddrinuse Address in use.
+ ErrnoAddrinuse
+ // ErrnoAddrnotavail Address not available.
+ ErrnoAddrnotavail
+ // ErrnoAfnosupport Address family not supported.
+ ErrnoAfnosupport
+ // ErrnoAgain Resource unavailable, or operation would block.
+ ErrnoAgain
+ // ErrnoAlready Connection already in progress.
+ ErrnoAlready
+ // ErrnoBadf Bad file descriptor.
+ ErrnoBadf
+ // ErrnoBadmsg Bad message.
+ ErrnoBadmsg
+ // ErrnoBusy Device or resource busy.
+ ErrnoBusy
+ // ErrnoCanceled Operation canceled.
+ ErrnoCanceled
+ // ErrnoChild No child processes.
+ ErrnoChild
+ // ErrnoConnaborted Connection aborted.
+ ErrnoConnaborted
+ // ErrnoConnrefused Connection refused.
+ ErrnoConnrefused
+ // ErrnoConnreset Connection reset.
+ ErrnoConnreset
+ // ErrnoDeadlk Resource deadlock would occur.
+ ErrnoDeadlk
+ // ErrnoDestaddrreq Destination address required.
+ ErrnoDestaddrreq
+ // ErrnoDom Mathematics argument out of domain of function.
+ ErrnoDom
+ // ErrnoDquot Reserved.
+ ErrnoDquot
+ // ErrnoExist File exists.
+ ErrnoExist
+ // ErrnoFault Bad address.
+ ErrnoFault
+ // ErrnoFbig File too large.
+ ErrnoFbig
+ // ErrnoHostunreach Host is unreachable.
+ ErrnoHostunreach
+ // ErrnoIdrm Identifier removed.
+ ErrnoIdrm
+ // ErrnoIlseq Illegal byte sequence.
+ ErrnoIlseq
+ // ErrnoInprogress Operation in progress.
+ ErrnoInprogress
+ // ErrnoIntr Interrupted function.
+ ErrnoIntr
+ // ErrnoInval Invalid argument.
+ ErrnoInval
+ // ErrnoIo I/O error.
+ ErrnoIo
+ // ErrnoIsconn Socket is connected.
+ ErrnoIsconn
+ // ErrnoIsdir Is a directory.
+ ErrnoIsdir
+ // ErrnoLoop Too many levels of symbolic links.
+ ErrnoLoop
+ // ErrnoMfile File descriptor value too large.
+ ErrnoMfile
+ // ErrnoMlink Too many links.
+ ErrnoMlink
+ // ErrnoMsgsize Message too large.
+ ErrnoMsgsize
+ // ErrnoMultihop Reserved.
+ ErrnoMultihop
+ // ErrnoNametoolong Filename too long.
+ ErrnoNametoolong
+ // ErrnoNetdown Network is down.
+ ErrnoNetdown
+ // ErrnoNetreset Connection aborted by network.
+ ErrnoNetreset
+ // ErrnoNetunreach Network unreachable.
+ ErrnoNetunreach
+ // ErrnoNfile Too many files open in system.
+ ErrnoNfile
+ // ErrnoNobufs No buffer space available.
+ ErrnoNobufs
+ // ErrnoNodev No such device.
+ ErrnoNodev
+ // ErrnoNoent No such file or directory.
+ ErrnoNoent
+ // ErrnoNoexec Executable file format error.
+ ErrnoNoexec
+ // ErrnoNolck No locks available.
+ ErrnoNolck
+ // ErrnoNolink Reserved.
+ ErrnoNolink
+ // ErrnoNomem Not enough space.
+ ErrnoNomem
+ // ErrnoNomsg No message of the desired type.
+ ErrnoNomsg
+ // ErrnoNoprotoopt No message of the desired type.
+ ErrnoNoprotoopt
+ // ErrnoNospc No space left on device.
+ ErrnoNospc
+ // ErrnoNosys function not supported.
+ ErrnoNosys
+ // ErrnoNotconn The socket is not connected.
+ ErrnoNotconn
+ // ErrnoNotdir Not a directory or a symbolic link to a directory.
+ ErrnoNotdir
+ // ErrnoNotempty Directory not empty.
+ ErrnoNotempty
+ // ErrnoNotrecoverable State not recoverable.
+ ErrnoNotrecoverable
+ // ErrnoNotsock Not a socket.
+ ErrnoNotsock
+ // ErrnoNotsup Not supported, or operation not supported on socket.
+ ErrnoNotsup
+ // ErrnoNotty Inappropriate I/O control operation.
+ ErrnoNotty
+ // ErrnoNxio No such device or address.
+ ErrnoNxio
+ // ErrnoOverflow Value too large to be stored in data type.
+ ErrnoOverflow
+ // ErrnoOwnerdead Previous owner died.
+ ErrnoOwnerdead
+ // ErrnoPerm Operation not permitted.
+ ErrnoPerm
+ // ErrnoPipe Broken pipe.
+ ErrnoPipe
+ // ErrnoProto Protocol error.
+ ErrnoProto
+ // ErrnoProtonosupport Protocol error.
+ ErrnoProtonosupport
+ // ErrnoPrototype Protocol wrong type for socket.
+ ErrnoPrototype
+ // ErrnoRange Result too large.
+ ErrnoRange
+ // ErrnoRofs Read-only file system.
+ ErrnoRofs
+ // ErrnoSpipe Invalid seek.
+ ErrnoSpipe
+ // ErrnoSrch No such process.
+ ErrnoSrch
+ // ErrnoStale Reserved.
+ ErrnoStale
+ // ErrnoTimedout Connection timed out.
+ ErrnoTimedout
+ // ErrnoTxtbsy Text file busy.
+ ErrnoTxtbsy
+ // ErrnoXdev Cross-device link.
+ ErrnoXdev
+
+ // Note: ErrnoNotcapable was removed by WASI maintainers.
+ // See https://github.com/WebAssembly/wasi-libc/pull/294
+)
+
+var errnoToString = [...]string{
+ "ESUCCESS",
+ "E2BIG",
+ "EACCES",
+ "EADDRINUSE",
+ "EADDRNOTAVAIL",
+ "EAFNOSUPPORT",
+ "EAGAIN",
+ "EALREADY",
+ "EBADF",
+ "EBADMSG",
+ "EBUSY",
+ "ECANCELED",
+ "ECHILD",
+ "ECONNABORTED",
+ "ECONNREFUSED",
+ "ECONNRESET",
+ "EDEADLK",
+ "EDESTADDRREQ",
+ "EDOM",
+ "EDQUOT",
+ "EEXIST",
+ "EFAULT",
+ "EFBIG",
+ "EHOSTUNREACH",
+ "EIDRM",
+ "EILSEQ",
+ "EINPROGRESS",
+ "EINTR",
+ "EINVAL",
+ "EIO",
+ "EISCONN",
+ "EISDIR",
+ "ELOOP",
+ "EMFILE",
+ "EMLINK",
+ "EMSGSIZE",
+ "EMULTIHOP",
+ "ENAMETOOLONG",
+ "ENETDOWN",
+ "ENETRESET",
+ "ENETUNREACH",
+ "ENFILE",
+ "ENOBUFS",
+ "ENODEV",
+ "ENOENT",
+ "ENOEXEC",
+ "ENOLCK",
+ "ENOLINK",
+ "ENOMEM",
+ "ENOMSG",
+ "ENOPROTOOPT",
+ "ENOSPC",
+ "ENOSYS",
+ "ENOTCONN",
+ "ENOTDIR",
+ "ENOTEMPTY",
+ "ENOTRECOVERABLE",
+ "ENOTSOCK",
+ "ENOTSUP",
+ "ENOTTY",
+ "ENXIO",
+ "EOVERFLOW",
+ "EOWNERDEAD",
+ "EPERM",
+ "EPIPE",
+ "EPROTO",
+ "EPROTONOSUPPORT",
+ "EPROTOTYPE",
+ "ERANGE",
+ "EROFS",
+ "ESPIPE",
+ "ESRCH",
+ "ESTALE",
+ "ETIMEDOUT",
+ "ETXTBSY",
+ "EXDEV",
+ "ENOTCAPABLE",
+}
+
+// ToErrno coerces the error to a WASI Errno.
+//
+// Note: Coercion isn't centralized in sys.FSContext because ABI use different
+// error codes. For example, wasi-filesystem doesn't map to these
+// Errno.
+func ToErrno(errno sys.Errno) Errno {
+ switch errno {
+ case 0:
+ return ErrnoSuccess
+ case sys.EACCES:
+ return ErrnoAcces
+ case sys.EAGAIN:
+ return ErrnoAgain
+ case sys.EBADF:
+ return ErrnoBadf
+ case sys.EEXIST:
+ return ErrnoExist
+ case sys.EFAULT:
+ return ErrnoFault
+ case sys.EINTR:
+ return ErrnoIntr
+ case sys.EINVAL:
+ return ErrnoInval
+ case sys.EIO:
+ return ErrnoIo
+ case sys.EISDIR:
+ return ErrnoIsdir
+ case sys.ELOOP:
+ return ErrnoLoop
+ case sys.ENAMETOOLONG:
+ return ErrnoNametoolong
+ case sys.ENOENT:
+ return ErrnoNoent
+ case sys.ENOSYS:
+ return ErrnoNosys
+ case sys.ENOTDIR:
+ return ErrnoNotdir
+ case sys.ERANGE:
+ return ErrnoRange
+ case sys.ENOTEMPTY:
+ return ErrnoNotempty
+ case sys.ENOTSOCK:
+ return ErrnoNotsock
+ case sys.ENOTSUP:
+ return ErrnoNotsup
+ case sys.EPERM:
+ return ErrnoPerm
+ case sys.EROFS:
+ return ErrnoRofs
+ default:
+ return ErrnoIo
+ }
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go
new file mode 100644
index 000000000..ed8df1edc
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go
@@ -0,0 +1,164 @@
+package wasip1
+
+import (
+ "fmt"
+)
+
+const (
+ FdAdviseName = "fd_advise"
+ FdAllocateName = "fd_allocate"
+ FdCloseName = "fd_close"
+ FdDatasyncName = "fd_datasync"
+ FdFdstatGetName = "fd_fdstat_get"
+ FdFdstatSetFlagsName = "fd_fdstat_set_flags"
+ FdFdstatSetRightsName = "fd_fdstat_set_rights"
+ FdFilestatGetName = "fd_filestat_get"
+ FdFilestatSetSizeName = "fd_filestat_set_size"
+ FdFilestatSetTimesName = "fd_filestat_set_times"
+ FdPreadName = "fd_pread"
+ FdPrestatGetName = "fd_prestat_get"
+ FdPrestatDirNameName = "fd_prestat_dir_name"
+ FdPwriteName = "fd_pwrite"
+ FdReadName = "fd_read"
+ FdReaddirName = "fd_readdir"
+ FdRenumberName = "fd_renumber"
+ FdSeekName = "fd_seek"
+ FdSyncName = "fd_sync"
+ FdTellName = "fd_tell"
+ FdWriteName = "fd_write"
+
+ PathCreateDirectoryName = "path_create_directory"
+ PathFilestatGetName = "path_filestat_get"
+ PathFilestatSetTimesName = "path_filestat_set_times"
+ PathLinkName = "path_link"
+ PathOpenName = "path_open"
+ PathReadlinkName = "path_readlink"
+ PathRemoveDirectoryName = "path_remove_directory"
+ PathRenameName = "path_rename"
+ PathSymlinkName = "path_symlink"
+ PathUnlinkFileName = "path_unlink_file"
+)
+
+// oflags are open flags used by path_open
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16
+const (
+ // O_CREAT creates a file if it does not exist.
+ O_CREAT uint16 = 1 << iota //nolint
+ // O_DIRECTORY fails if not a directory.
+ O_DIRECTORY
+ // O_EXCL fails if file already exists.
+ O_EXCL //nolint
+ // O_TRUNC truncates the file to size 0.
+ O_TRUNC //nolint
+)
+
+func OflagsString(oflags int) string {
+ return flagsString(oflagNames[:], oflags)
+}
+
+var oflagNames = [...]string{
+ "CREAT",
+ "DIRECTORY",
+ "EXCL",
+ "TRUNC",
+}
+
+// file descriptor flags
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdflags
+const (
+ FD_APPEND uint16 = 1 << iota //nolint
+ FD_DSYNC
+ FD_NONBLOCK
+ FD_RSYNC
+ FD_SYNC
+)
+
+func FdFlagsString(fdflags int) string {
+ return flagsString(fdflagNames[:], fdflags)
+}
+
+var fdflagNames = [...]string{
+ "APPEND",
+ "DSYNC",
+ "NONBLOCK",
+ "RSYNC",
+ "SYNC",
+}
+
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags
+const (
+ // LOOKUP_SYMLINK_FOLLOW expands a path if it resolves into a symbolic
+ // link.
+ LOOKUP_SYMLINK_FOLLOW uint16 = 1 << iota //nolint
+)
+
+var lookupflagNames = [...]string{
+ "SYMLINK_FOLLOW",
+}
+
+func LookupflagsString(lookupflags int) string {
+ return flagsString(lookupflagNames[:], lookupflags)
+}
+
+// DirentSize is the size of the dirent struct, which should be followed by the
+// length of a file name.
+const DirentSize = uint32(24)
+
+const (
+ FILETYPE_UNKNOWN uint8 = iota
+ FILETYPE_BLOCK_DEVICE
+ FILETYPE_CHARACTER_DEVICE
+ FILETYPE_DIRECTORY
+ FILETYPE_REGULAR_FILE
+ FILETYPE_SOCKET_DGRAM
+ FILETYPE_SOCKET_STREAM
+ FILETYPE_SYMBOLIC_LINK
+)
+
+// FiletypeName returns string name of the file type.
+func FiletypeName(filetype uint8) string {
+ if int(filetype) < len(filetypeToString) {
+ return filetypeToString[filetype]
+ }
+ return fmt.Sprintf("filetype(%d)", filetype)
+}
+
+var filetypeToString = [...]string{
+ "UNKNOWN",
+ "BLOCK_DEVICE",
+ "CHARACTER_DEVICE",
+ "DIRECTORY",
+ "REGULAR_FILE",
+ "SOCKET_DGRAM",
+ "SOCKET_STREAM",
+ "SYMBOLIC_LINK",
+}
+
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fstflags
+const (
+ FstflagsAtim uint16 = 1 << iota
+ FstflagsAtimNow
+ FstflagsMtim
+ FstflagsMtimNow
+)
+
+var fstflagNames = [...]string{
+ "ATIM",
+ "ATIM_NOW",
+ "MTIM",
+ "MTIM_NOW",
+}
+
+func FstflagsString(fdflags int) string {
+ return flagsString(fstflagNames[:], fdflags)
+}
+
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-advice-enumu8
+const (
+ FdAdviceNormal byte = iota
+ FdAdviceSequential
+ FdAdviceRandom
+ FdAdviceWillNeed
+ FdAdviceDontNeed
+ FdAdviceNoReuse
+)
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go
new file mode 100644
index 000000000..9bde768f2
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go
@@ -0,0 +1,15 @@
+package wasip1
+
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-eventtype-enumu8
+const (
+ // EventTypeClock is the timeout event named "name".
+ EventTypeClock = iota
+ // EventTypeFdRead is the data available event named "fd_read".
+ EventTypeFdRead
+ // EventTypeFdWrite is the capacity available event named "fd_write".
+ EventTypeFdWrite
+)
+
+const (
+ PollOneoffName = "poll_oneoff"
+)
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go
new file mode 100644
index 000000000..50b040c98
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go
@@ -0,0 +1,6 @@
+package wasip1
+
+const (
+ ProcExitName = "proc_exit"
+ ProcRaiseName = "proc_raise"
+)
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go
new file mode 100644
index 000000000..236453374
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go
@@ -0,0 +1,3 @@
+package wasip1
+
+const RandomGetName = "random_get"
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go
new file mode 100644
index 000000000..2ab56c604
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go
@@ -0,0 +1,148 @@
+package wasip1
+
+// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-rights-flagsu64
+const (
+ // RIGHT_FD_DATASYNC is the right to invoke fd_datasync. If RIGHT_PATH_OPEN
+ // is set, includes the right to invoke path_open with FD_DSYNC.
+ RIGHT_FD_DATASYNC uint32 = 1 << iota //nolint
+
+ // RIGHT_FD_READ is he right to invoke fd_read and sock_recv. If
+ // RIGHT_FD_SYNC is set, includes the right to invoke fd_pread.
+ RIGHT_FD_READ
+
+ // RIGHT_FD_SEEK is the right to invoke fd_seek. This flag implies
+ // RIGHT_FD_TELL.
+ RIGHT_FD_SEEK
+
+ // RIGHT_FDSTAT_SET_FLAGS is the right to invoke fd_fdstat_set_flags.
+ RIGHT_FDSTAT_SET_FLAGS
+
+ // RIGHT_FD_SYNC The right to invoke fd_sync. If path_open is set, includes
+ // the right to invoke path_open with FD_RSYNC and FD_DSYNC.
+ RIGHT_FD_SYNC
+
+ // RIGHT_FD_TELL is the right to invoke fd_seek in such a way that the file
+ // offset remains unaltered (i.e., whence::cur with offset zero), or to
+ // invoke fd_tell.
+ RIGHT_FD_TELL
+
+ // RIGHT_FD_WRITE is the right to invoke fd_write and sock_send. If
+ // RIGHT_FD_SEEK is set, includes the right to invoke fd_pwrite.
+ RIGHT_FD_WRITE
+
+ // RIGHT_FD_ADVISE is the right to invoke fd_advise.
+ RIGHT_FD_ADVISE
+
+ // RIGHT_FD_ALLOCATE is the right to invoke fd_allocate.
+ RIGHT_FD_ALLOCATE
+
+ // RIGHT_PATH_CREATE_DIRECTORY is the right to invoke
+ // path_create_directory.
+ RIGHT_PATH_CREATE_DIRECTORY
+
+ // RIGHT_PATH_CREATE_FILE when RIGHT_PATH_OPEN is set, the right to invoke
+ // path_open with O_CREAT.
+ RIGHT_PATH_CREATE_FILE
+
+ // RIGHT_PATH_LINK_SOURCE is the right to invoke path_link with the file
+ // descriptor as the source directory.
+ RIGHT_PATH_LINK_SOURCE
+
+ // RIGHT_PATH_LINK_TARGET is the right to invoke path_link with the file
+ // descriptor as the target directory.
+ RIGHT_PATH_LINK_TARGET
+
+ // RIGHT_PATH_OPEN is the right to invoke path_open.
+ RIGHT_PATH_OPEN
+
+ // RIGHT_FD_READDIR is the right to invoke fd_readdir.
+ RIGHT_FD_READDIR
+
+ // RIGHT_PATH_READLINK is the right to invoke path_readlink.
+ RIGHT_PATH_READLINK
+
+ // RIGHT_PATH_RENAME_SOURCE is the right to invoke path_rename with the
+ // file descriptor as the source directory.
+ RIGHT_PATH_RENAME_SOURCE
+
+ // RIGHT_PATH_RENAME_TARGET is the right to invoke path_rename with the
+ // file descriptor as the target directory.
+ RIGHT_PATH_RENAME_TARGET
+
+ // RIGHT_PATH_FILESTAT_GET is the right to invoke path_filestat_get.
+ RIGHT_PATH_FILESTAT_GET
+
+ // RIGHT_PATH_FILESTAT_SET_SIZE is the right to change a file's size (there
+ // is no path_filestat_set_size). If RIGHT_PATH_OPEN is set, includes the
+ // right to invoke path_open with O_TRUNC.
+ RIGHT_PATH_FILESTAT_SET_SIZE
+
+ // RIGHT_PATH_FILESTAT_SET_TIMES is the right to invoke
+ // path_filestat_set_times.
+ RIGHT_PATH_FILESTAT_SET_TIMES
+
+ // RIGHT_FD_FILESTAT_GET is the right to invoke fd_filestat_get.
+ RIGHT_FD_FILESTAT_GET
+
+ // RIGHT_FD_FILESTAT_SET_SIZE is the right to invoke fd_filestat_set_size.
+ RIGHT_FD_FILESTAT_SET_SIZE
+
+ // RIGHT_FD_FILESTAT_SET_TIMES is the right to invoke
+ // fd_filestat_set_times.
+ RIGHT_FD_FILESTAT_SET_TIMES
+
+ // RIGHT_PATH_SYMLINK is the right to invoke path_symlink.
+ RIGHT_PATH_SYMLINK
+
+ // RIGHT_PATH_REMOVE_DIRECTORY is the right to invoke
+ // path_remove_directory.
+ RIGHT_PATH_REMOVE_DIRECTORY
+
+ // RIGHT_PATH_UNLINK_FILE is the right to invoke path_unlink_file.
+ RIGHT_PATH_UNLINK_FILE
+
+ // RIGHT_POLL_FD_READWRITE when RIGHT_FD_READ is set, includes the right to
+ // invoke poll_oneoff to subscribe to eventtype::fd_read. If RIGHT_FD_WRITE
+ // is set, includes the right to invoke poll_oneoff to subscribe to
+ // eventtype::fd_write.
+ RIGHT_POLL_FD_READWRITE
+
+ // RIGHT_SOCK_SHUTDOWN is the right to invoke sock_shutdown.
+ RIGHT_SOCK_SHUTDOWN
+)
+
+func RightsString(rights int) string {
+ return flagsString(rightNames[:], rights)
+}
+
+var rightNames = [...]string{
+ "FD_DATASYNC",
+ "FD_READ",
+ "FD_SEEK",
+ "FDSTAT_SET_FLAGS",
+ "FD_SYNC",
+ "FD_TELL",
+ "FD_WRITE",
+ "FD_ADVISE",
+ "FD_ALLOCATE",
+ "PATH_CREATE_DIRECTORY",
+ "PATH_CREATE_FILE",
+ "PATH_LINK_SOURCE",
+ "PATH_LINK_TARGET",
+ "PATH_OPEN",
+ "FD_READDIR",
+ "PATH_READLINK",
+ "PATH_RENAME_SOURCE",
+ "PATH_RENAME_TARGET",
+ "PATH_FILESTAT_GET",
+ "PATH_FILESTAT_SET_SIZE",
+ "PATH_FILESTAT_SET_TIMES",
+ "FD_FILESTAT_GET",
+ "FD_FILESTAT_SET_SIZE",
+ "FD_FILESTAT_SET_TIMES",
+ "PATH_SYMLINK",
+ "PATH_REMOVE_DIRECTORY",
+ "PATH_UNLINK_FILE",
+ "POLL_FD_READWRITE",
+ "SOCK_SHUTDOWN",
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go
new file mode 100644
index 000000000..bc6e39385
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go
@@ -0,0 +1,3 @@
+package wasip1
+
+const SchedYieldName = "sched_yield"
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go
new file mode 100644
index 000000000..90d33ece8
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go
@@ -0,0 +1,71 @@
+package wasip1
+
+import "strconv"
+
+const (
+ SockAcceptName = "sock_accept"
+ SockRecvName = "sock_recv"
+ SockSendName = "sock_send"
+ SockShutdownName = "sock_shutdown"
+)
+
+// SD Flags indicate which channels on a socket to shut down.
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sdflags-flagsu8
+const (
+ // SD_RD disables further receive operations.
+ SD_RD uint8 = 1 << iota //nolint
+ // SD_WR disables further send operations.
+ SD_WR
+)
+
+func SdFlagsString(sdflags int) string {
+ return flagsString(sdflagNames[:], sdflags)
+}
+
+var sdflagNames = [...]string{
+ "RD",
+ "WR",
+}
+
+// SI Flags are flags provided to sock_send. As there are currently no flags defined, it must be set to zero.
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-siflags-u16
+
+func SiFlagsString(siflags int) string {
+ if siflags == 0 {
+ return ""
+ }
+ return strconv.Itoa(siflags)
+}
+
+// RI Flags are flags provided to sock_recv.
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-riflags-flagsu16
+const (
+ // RI_RECV_PEEK returns the message without removing it from the socket's receive queue
+ RI_RECV_PEEK uint8 = 1 << iota //nolint
+ // RI_RECV_WAITALL on byte-stream sockets, block until the full amount of data can be returned.
+ RI_RECV_WAITALL
+)
+
+func RiFlagsString(riflags int) string {
+ return flagsString(riflagNames[:], riflags)
+}
+
+var riflagNames = [...]string{
+ "RECV_PEEK",
+ "RECV_WAITALL",
+}
+
+// RO Flags are flags returned by sock_recv.
+// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-roflags-flagsu16
+const (
+ // RO_RECV_DATA_TRUNCATED is returned by sock_recv when message data has been truncated.
+ RO_RECV_DATA_TRUNCATED uint8 = 1 << iota //nolint
+)
+
+func RoFlagsString(roflags int) string {
+ return flagsString(roflagNames[:], roflags)
+}
+
+var roflagNames = [...]string{
+ "RECV_DATA_TRUNCATED",
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go
new file mode 100644
index 000000000..299feea2f
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go
@@ -0,0 +1,26 @@
+// Package wasip1 is a helper to remove package cycles re-using constants.
+package wasip1
+
+import (
+ "strings"
+)
+
+// InternalModuleName is not named ModuleName, to avoid a clash on dot imports.
+const InternalModuleName = "wasi_snapshot_preview1"
+
+func flagsString(names []string, f int) string {
+ var builder strings.Builder
+ first := true
+ for i, sf := range names {
+ target := 1 << i
+ if target&f != 0 {
+ if !first {
+ builder.WriteByte('|')
+ } else {
+ first = false
+ }
+ builder.WriteString(sf)
+ }
+ }
+ return builder.String()
+}