diff options
author | 2024-07-12 09:39:47 +0000 | |
---|---|---|
committer | 2024-07-12 09:39:47 +0000 | |
commit | cde2fb6244a791b3c5b746112e3a8be3a79f39a4 (patch) | |
tree | 6079d6fb66d90ffbe8c1623525bb86829c162459 /vendor/github.com/tetratelabs/wazero | |
parent | [chore] Add interaction policy gtsmodels (#3075) (diff) | |
download | gotosocial-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')
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() +} |