summaryrefslogtreecommitdiff
path: root/vendor/github.com/tetratelabs/wazero/internal/sys
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/internal/sys')
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/sys/fs.go457
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go151
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go128
-rw-r--r--vendor/github.com/tetratelabs/wazero/internal/sys/sys.go228
4 files changed, 964 insertions, 0 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go b/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go
new file mode 100644
index 000000000..157de788f
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go
@@ -0,0 +1,457 @@
+package sys
+
+import (
+ "io"
+ "io/fs"
+ "net"
+
+ "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/descriptor"
+ "github.com/tetratelabs/wazero/internal/fsapi"
+ socketapi "github.com/tetratelabs/wazero/internal/sock"
+ "github.com/tetratelabs/wazero/internal/sysfs"
+)
+
+const (
+ FdStdin int32 = iota
+ FdStdout
+ FdStderr
+ // FdPreopen is the file descriptor of the first pre-opened directory.
+ //
+ // # Why file descriptor 3?
+ //
+ // While not specified, the most common WASI implementation, wasi-libc,
+ // expects POSIX style file descriptor allocation, where the lowest
+ // available number is used to open the next file. Since 1 and 2 are taken
+ // by stdout and stderr, the next is 3.
+ // - https://github.com/WebAssembly/WASI/issues/122
+ // - https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_14
+ // - https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-16/libc-bottom-half/sources/preopens.c#L215
+ FdPreopen
+)
+
+const modeDevice = fs.ModeDevice | 0o640
+
+// FileEntry maps a path to an open file in a file system.
+type FileEntry struct {
+ // Name is the name of the directory up to its pre-open, or the pre-open
+ // name itself when IsPreopen.
+ //
+ // # Notes
+ //
+ // - This can drift on rename.
+ // - This relates to the guest path, which is not the real file path
+ // except if the entire host filesystem was made available.
+ Name string
+
+ // IsPreopen is a directory that is lazily opened.
+ IsPreopen bool
+
+ // FS is the filesystem associated with the pre-open.
+ FS sys.FS
+
+ // File is always non-nil.
+ File fsapi.File
+
+ // direntCache is nil until DirentCache was called.
+ direntCache *DirentCache
+}
+
+// DirentCache gets or creates a DirentCache for this file or returns an error.
+//
+// # Errors
+//
+// A zero sys.Errno is success. The below are expected otherwise:
+// - sys.ENOSYS: the implementation does not support this function.
+// - sys.EBADF: the dir was closed or not readable.
+// - sys.ENOTDIR: the file was not a directory.
+//
+// # Notes
+//
+// - See /RATIONALE.md for design notes.
+func (f *FileEntry) DirentCache() (*DirentCache, sys.Errno) {
+ if dir := f.direntCache; dir != nil {
+ return dir, 0
+ }
+
+ // Require the file to be a directory vs a late error on the same.
+ if isDir, errno := f.File.IsDir(); errno != 0 {
+ return nil, errno
+ } else if !isDir {
+ return nil, sys.ENOTDIR
+ }
+
+ // Generate the dotEntries only once.
+ if dotEntries, errno := synthesizeDotEntries(f); errno != 0 {
+ return nil, errno
+ } else {
+ f.direntCache = &DirentCache{f: f.File, dotEntries: dotEntries}
+ }
+
+ return f.direntCache, 0
+}
+
+// DirentCache is a caching abstraction of sys.File Readdir.
+//
+// This is special-cased for "wasi_snapshot_preview1.fd_readdir", and may be
+// unneeded, or require changes, to support preview1 or preview2.
+// - The position of the dirents are serialized as `d_next`. For reasons
+// described below, any may need to be re-read. This accepts any positions
+// in the cache, rather than track the position of the last dirent.
+// - dot entries ("." and "..") must be returned. See /RATIONALE.md for why.
+// - An sys.Dirent Name is variable length, it could exceed memory size and
+// need to be re-read.
+// - Multiple dirents may be returned. It is more efficient to read from the
+// underlying file in bulk vs one-at-a-time.
+//
+// The last results returned by Read are cached, but entries before that
+// position are not. This support re-reading entries that couldn't fit into
+// memory without accidentally caching all entries in a large directory. This
+// approach is sometimes called a sliding window.
+type DirentCache struct {
+ // f is the underlying file
+ f sys.File
+
+ // dotEntries are the "." and ".." entries added when the directory is
+ // initialized.
+ dotEntries []sys.Dirent
+
+ // dirents are the potentially unread directory entries.
+ //
+ // Internal detail: nil is different from zero length. Zero length is an
+ // exhausted directory (eof). nil means the re-read.
+ dirents []sys.Dirent
+
+ // countRead is the total count of dirents read since last rewind.
+ countRead uint64
+
+ // eof is true when the underlying file is at EOF. This avoids re-reading
+ // the directory when it is exhausted. Entires in an exhausted directory
+ // are not visible until it is rewound via calling Read with `pos==0`.
+ eof bool
+}
+
+// synthesizeDotEntries generates a slice of the two elements "." and "..".
+func synthesizeDotEntries(f *FileEntry) ([]sys.Dirent, sys.Errno) {
+ dotIno, errno := f.File.Ino()
+ if errno != 0 {
+ return nil, errno
+ }
+ result := [2]sys.Dirent{}
+ result[0] = sys.Dirent{Name: ".", Ino: dotIno, Type: fs.ModeDir}
+ // See /RATIONALE.md for why we don't attempt to get an inode for ".." and
+ // why in wasi-libc this won't fan-out either.
+ result[1] = sys.Dirent{Name: "..", Ino: 0, Type: fs.ModeDir}
+ return result[:], 0
+}
+
+// exhaustedDirents avoids allocating empty slices.
+var exhaustedDirents = [0]sys.Dirent{}
+
+// Read is similar to and returns the same errors as `Readdir` on sys.File.
+// The main difference is this caches entries returned, resulting in multiple
+// valid positions to read from.
+//
+// When zero, `pos` means rewind to the beginning of this directory. This
+// implies a rewind (Seek to zero on the underlying sys.File), unless the
+// initial entries are still cached.
+//
+// When non-zero, `pos` is the zero based index of all dirents returned since
+// last rewind. Only entries beginning at `pos` are cached for subsequent
+// calls. A non-zero `pos` before the cache returns sys.ENOENT for reasons
+// described on DirentCache documentation.
+//
+// Up to `n` entries are cached and returned. When `n` exceeds the cache, the
+// difference are read from the underlying sys.File via `Readdir`. EOF is
+// when `len(dirents)` returned are less than `n`.
+func (d *DirentCache) Read(pos uint64, n uint32) (dirents []sys.Dirent, errno sys.Errno) {
+ switch {
+ case pos > d.countRead: // farther than read or negative coerced to uint64.
+ return nil, sys.ENOENT
+ case pos == 0 && d.dirents != nil:
+ // Rewind if we have already read entries. This allows us to see new
+ // entries added after the directory was opened.
+ if _, errno = d.f.Seek(0, io.SeekStart); errno != 0 {
+ return
+ }
+ d.dirents = nil // dump cache
+ d.countRead = 0
+ }
+
+ if n == 0 {
+ return // special case no entries.
+ }
+
+ if d.dirents == nil {
+ // Always populate dot entries, which makes min len(dirents) == 2.
+ d.dirents = d.dotEntries
+ d.countRead = 2
+ d.eof = false
+
+ if countToRead := int(n - 2); countToRead <= 0 {
+ return
+ } else if dirents, errno = d.f.Readdir(countToRead); errno != 0 {
+ return
+ } else if countRead := len(dirents); countRead > 0 {
+ d.eof = countRead < countToRead
+ d.dirents = append(d.dotEntries, dirents...)
+ d.countRead += uint64(countRead)
+ }
+
+ return d.cachedDirents(n), 0
+ }
+
+ // Reset our cache to the first entry being read.
+ cacheStart := d.countRead - uint64(len(d.dirents))
+ if pos < cacheStart {
+ // We don't currently allow reads before our cache because Seek(0) is
+ // the only portable way. Doing otherwise requires skipping, which we
+ // won't do unless wasi-testsuite starts requiring it. Implementing
+ // this would allow re-reading a large directory, so care would be
+ // needed to not buffer the entire directory in memory while skipping.
+ errno = sys.ENOENT
+ return
+ } else if posInCache := pos - cacheStart; posInCache != 0 {
+ if uint64(len(d.dirents)) == posInCache {
+ // Avoid allocation re-slicing to zero length.
+ d.dirents = exhaustedDirents[:]
+ } else {
+ d.dirents = d.dirents[posInCache:]
+ }
+ }
+
+ // See if we need more entries.
+ if countToRead := int(n) - len(d.dirents); countToRead > 0 && !d.eof {
+ // Try to read more, which could fail.
+ if dirents, errno = d.f.Readdir(countToRead); errno != 0 {
+ return
+ }
+
+ // Append the next read entries if we weren't at EOF.
+ if countRead := len(dirents); countRead > 0 {
+ d.eof = countRead < countToRead
+ d.dirents = append(d.dirents, dirents...)
+ d.countRead += uint64(countRead)
+ }
+ }
+
+ return d.cachedDirents(n), 0
+}
+
+// cachedDirents returns up to `n` dirents from the cache.
+func (d *DirentCache) cachedDirents(n uint32) []sys.Dirent {
+ direntCount := uint32(len(d.dirents))
+ switch {
+ case direntCount == 0:
+ return nil
+ case direntCount > n:
+ return d.dirents[:n]
+ }
+ return d.dirents
+}
+
+type FSContext struct {
+ // openedFiles is a map of file descriptor numbers (>=FdPreopen) to open files
+ // (or directories) and defaults to empty.
+ // TODO: This is unguarded, so not goroutine-safe!
+ openedFiles FileTable
+}
+
+// FileTable is a specialization of the descriptor.Table type used to map file
+// descriptors to file entries.
+type FileTable = descriptor.Table[int32, *FileEntry]
+
+// LookupFile returns a file if it is in the table.
+func (c *FSContext) LookupFile(fd int32) (*FileEntry, bool) {
+ return c.openedFiles.Lookup(fd)
+}
+
+// OpenFile opens the file into the table and returns its file descriptor.
+// The result must be closed by CloseFile or Close.
+func (c *FSContext) OpenFile(fs sys.FS, path string, flag sys.Oflag, perm fs.FileMode) (int32, sys.Errno) {
+ if f, errno := fs.OpenFile(path, flag, perm); errno != 0 {
+ return 0, errno
+ } else {
+ fe := &FileEntry{FS: fs, File: fsapi.Adapt(f)}
+ if path == "/" || path == "." {
+ fe.Name = ""
+ } else {
+ fe.Name = path
+ }
+ if newFD, ok := c.openedFiles.Insert(fe); !ok {
+ return 0, sys.EBADF
+ } else {
+ return newFD, 0
+ }
+ }
+}
+
+// Renumber assigns the file pointed by the descriptor `from` to `to`.
+func (c *FSContext) Renumber(from, to int32) sys.Errno {
+ fromFile, ok := c.openedFiles.Lookup(from)
+ if !ok || to < 0 {
+ return sys.EBADF
+ } else if fromFile.IsPreopen {
+ return sys.ENOTSUP
+ }
+
+ // If toFile is already open, we close it to prevent windows lock issues.
+ //
+ // The doc is unclear and other implementations do nothing for already-opened To FDs.
+ // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
+ // https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-common/src/snapshots/preview_1.rs#L531-L546
+ if toFile, ok := c.openedFiles.Lookup(to); ok {
+ if toFile.IsPreopen {
+ return sys.ENOTSUP
+ }
+ _ = toFile.File.Close()
+ }
+
+ c.openedFiles.Delete(from)
+ if !c.openedFiles.InsertAt(fromFile, to) {
+ return sys.EBADF
+ }
+ return 0
+}
+
+// SockAccept accepts a sock.TCPConn into the file table and returns its file
+// descriptor.
+func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, sys.Errno) {
+ var sock socketapi.TCPSock
+ if e, ok := c.LookupFile(sockFD); !ok || !e.IsPreopen {
+ return 0, sys.EBADF // Not a preopen
+ } else if sock, ok = e.File.(socketapi.TCPSock); !ok {
+ return 0, sys.EBADF // Not a sock
+ }
+
+ conn, errno := sock.Accept()
+ if errno != 0 {
+ return 0, errno
+ }
+
+ fe := &FileEntry{File: fsapi.Adapt(conn)}
+
+ if nonblock {
+ if errno = fe.File.SetNonblock(true); errno != 0 {
+ _ = conn.Close()
+ return 0, errno
+ }
+ }
+
+ if newFD, ok := c.openedFiles.Insert(fe); !ok {
+ return 0, sys.EBADF
+ } else {
+ return newFD, 0
+ }
+}
+
+// CloseFile returns any error closing the existing file.
+func (c *FSContext) CloseFile(fd int32) (errno sys.Errno) {
+ f, ok := c.openedFiles.Lookup(fd)
+ if !ok {
+ return sys.EBADF
+ }
+ if errno = f.File.Close(); errno != 0 {
+ return errno
+ }
+ c.openedFiles.Delete(fd)
+ return errno
+}
+
+// Close implements io.Closer
+func (c *FSContext) Close() (err error) {
+ // Close any files opened in this context
+ c.openedFiles.Range(func(fd int32, entry *FileEntry) bool {
+ if errno := entry.File.Close(); errno != 0 {
+ err = errno // This means err returned == the last non-nil error.
+ }
+ return true
+ })
+ // A closed FSContext cannot be reused so clear the state.
+ c.openedFiles = FileTable{}
+ return
+}
+
+// InitFSContext initializes a FSContext with stdio streams and optional
+// pre-opened filesystems and TCP listeners.
+func (c *Context) InitFSContext(
+ stdin io.Reader,
+ stdout, stderr io.Writer,
+ fs []sys.FS, guestPaths []string,
+ tcpListeners []*net.TCPListener,
+) (err error) {
+ inFile, err := stdinFileEntry(stdin)
+ if err != nil {
+ return err
+ }
+ c.fsc.openedFiles.Insert(inFile)
+ outWriter, err := stdioWriterFileEntry("stdout", stdout)
+ if err != nil {
+ return err
+ }
+ c.fsc.openedFiles.Insert(outWriter)
+ errWriter, err := stdioWriterFileEntry("stderr", stderr)
+ if err != nil {
+ return err
+ }
+ c.fsc.openedFiles.Insert(errWriter)
+
+ for i, f := range fs {
+ guestPath := guestPaths[i]
+
+ if StripPrefixesAndTrailingSlash(guestPath) == "" {
+ // Default to bind to '/' when guestPath is effectively empty.
+ guestPath = "/"
+ }
+ c.fsc.openedFiles.Insert(&FileEntry{
+ FS: f,
+ Name: guestPath,
+ IsPreopen: true,
+ File: &lazyDir{fs: f},
+ })
+ }
+
+ for _, tl := range tcpListeners {
+ c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: fsapi.Adapt(sysfs.NewTCPListenerFile(tl))})
+ }
+ return nil
+}
+
+// StripPrefixesAndTrailingSlash skips any leading "./" or "/" such that the
+// result index begins with another string. A result of "." coerces to the
+// empty string "" because the current directory is handled by the guest.
+//
+// Results are the offset/len pair which is an optimization to avoid re-slicing
+// overhead, as this function is called for every path operation.
+//
+// Note: Relative paths should be handled by the guest, as that's what knows
+// what the current directory is. However, paths that escape the current
+// directory e.g. "../.." have been found in `tinygo test` and this
+// implementation takes care to avoid it.
+func StripPrefixesAndTrailingSlash(path string) string {
+ // strip trailing slashes
+ pathLen := len(path)
+ for ; pathLen > 0 && path[pathLen-1] == '/'; pathLen-- {
+ }
+
+ pathI := 0
+loop:
+ for pathI < pathLen {
+ switch path[pathI] {
+ case '/':
+ pathI++
+ case '.':
+ nextI := pathI + 1
+ if nextI < pathLen && path[nextI] == '/' {
+ pathI = nextI + 1
+ } else if nextI == pathLen {
+ pathI = nextI
+ } else {
+ break loop
+ }
+ default:
+ break loop
+ }
+ }
+ return path[pathI:pathLen]
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go b/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go
new file mode 100644
index 000000000..fe233d29e
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go
@@ -0,0 +1,151 @@
+package sys
+
+import (
+ experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/fsapi"
+ "github.com/tetratelabs/wazero/sys"
+)
+
+// compile-time check to ensure lazyDir implements sys.File.
+var _ experimentalsys.File = (*lazyDir)(nil)
+
+type lazyDir struct {
+ experimentalsys.DirFile
+
+ fs experimentalsys.FS
+ f experimentalsys.File
+}
+
+// Dev implements the same method as documented on sys.File
+func (d *lazyDir) Dev() (uint64, experimentalsys.Errno) {
+ if f, ok := d.file(); !ok {
+ return 0, experimentalsys.EBADF
+ } else {
+ return f.Dev()
+ }
+}
+
+// Ino implements the same method as documented on sys.File
+func (d *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) {
+ if f, ok := d.file(); !ok {
+ return 0, experimentalsys.EBADF
+ } else {
+ return f.Ino()
+ }
+}
+
+// IsDir implements the same method as documented on sys.File
+func (d *lazyDir) IsDir() (bool, experimentalsys.Errno) {
+ // Note: we don't return a constant because we don't know if this is really
+ // backed by a dir, until the first call.
+ if f, ok := d.file(); !ok {
+ return false, experimentalsys.EBADF
+ } else {
+ return f.IsDir()
+ }
+}
+
+// IsAppend implements the same method as documented on sys.File
+func (d *lazyDir) IsAppend() bool {
+ return false
+}
+
+// SetAppend implements the same method as documented on sys.File
+func (d *lazyDir) SetAppend(bool) experimentalsys.Errno {
+ return experimentalsys.EISDIR
+}
+
+// Seek implements the same method as documented on sys.File
+func (d *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) {
+ if f, ok := d.file(); !ok {
+ return 0, experimentalsys.EBADF
+ } else {
+ return f.Seek(offset, whence)
+ }
+}
+
+// Stat implements the same method as documented on sys.File
+func (d *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) {
+ if f, ok := d.file(); !ok {
+ return sys.Stat_t{}, experimentalsys.EBADF
+ } else {
+ return f.Stat()
+ }
+}
+
+// Readdir implements the same method as documented on sys.File
+func (d *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) {
+ if f, ok := d.file(); !ok {
+ return nil, experimentalsys.EBADF
+ } else {
+ return f.Readdir(n)
+ }
+}
+
+// Sync implements the same method as documented on sys.File
+func (d *lazyDir) Sync() experimentalsys.Errno {
+ if f, ok := d.file(); !ok {
+ return experimentalsys.EBADF
+ } else {
+ return f.Sync()
+ }
+}
+
+// Datasync implements the same method as documented on sys.File
+func (d *lazyDir) Datasync() experimentalsys.Errno {
+ if f, ok := d.file(); !ok {
+ return experimentalsys.EBADF
+ } else {
+ return f.Datasync()
+ }
+}
+
+// Utimens implements the same method as documented on sys.File
+func (d *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno {
+ if f, ok := d.file(); !ok {
+ return experimentalsys.EBADF
+ } else {
+ return f.Utimens(atim, mtim)
+ }
+}
+
+// file returns the underlying file or false if it doesn't exist.
+func (d *lazyDir) file() (experimentalsys.File, bool) {
+ if f := d.f; d.f != nil {
+ return f, true
+ }
+ var errno experimentalsys.Errno
+ d.f, errno = d.fs.OpenFile(".", experimentalsys.O_RDONLY, 0)
+ switch errno {
+ case 0:
+ return d.f, true
+ case experimentalsys.ENOENT:
+ return nil, false
+ default:
+ panic(errno) // unexpected
+ }
+}
+
+// Close implements fs.File
+func (d *lazyDir) Close() experimentalsys.Errno {
+ f := d.f
+ if f == nil {
+ return 0 // never opened
+ }
+ return f.Close()
+}
+
+// IsNonblock implements the same method as documented on fsapi.File
+func (d *lazyDir) IsNonblock() bool {
+ return false
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (d *lazyDir) SetNonblock(bool) experimentalsys.Errno {
+ return experimentalsys.EISDIR
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (d *lazyDir) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) {
+ return false, experimentalsys.ENOSYS
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go b/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go
new file mode 100644
index 000000000..32c33661e
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go
@@ -0,0 +1,128 @@
+package sys
+
+import (
+ "io"
+ "os"
+
+ experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/fsapi"
+ "github.com/tetratelabs/wazero/internal/sysfs"
+ "github.com/tetratelabs/wazero/sys"
+)
+
+// StdinFile is a fs.ModeDevice file for use implementing FdStdin.
+// This is safer than reading from os.DevNull as it can never overrun
+// operating system file descriptors.
+type StdinFile struct {
+ noopStdinFile
+ io.Reader
+}
+
+// Read implements the same method as documented on sys.File
+func (f *StdinFile) Read(buf []byte) (int, experimentalsys.Errno) {
+ n, err := f.Reader.Read(buf)
+ return n, experimentalsys.UnwrapOSError(err)
+}
+
+type writerFile struct {
+ noopStdoutFile
+
+ w io.Writer
+}
+
+// Write implements the same method as documented on sys.File
+func (f *writerFile) Write(buf []byte) (int, experimentalsys.Errno) {
+ n, err := f.w.Write(buf)
+ return n, experimentalsys.UnwrapOSError(err)
+}
+
+// noopStdinFile is a fs.ModeDevice file for use implementing FdStdin. This is
+// safer than reading from os.DevNull as it can never overrun operating system
+// file descriptors.
+type noopStdinFile struct {
+ noopStdioFile
+}
+
+// Read implements the same method as documented on sys.File
+func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) {
+ return 0, 0 // Always EOF
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (noopStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
+ if flag != fsapi.POLLIN {
+ return false, experimentalsys.ENOTSUP
+ }
+ return true, 0 // always ready to read nothing
+}
+
+// noopStdoutFile is a fs.ModeDevice file for use implementing FdStdout and
+// FdStderr.
+type noopStdoutFile struct {
+ noopStdioFile
+}
+
+// Write implements the same method as documented on sys.File
+func (noopStdoutFile) Write(buf []byte) (int, experimentalsys.Errno) {
+ return len(buf), 0 // same as io.Discard
+}
+
+type noopStdioFile struct {
+ experimentalsys.UnimplementedFile
+}
+
+// Stat implements the same method as documented on sys.File
+func (noopStdioFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
+ return sys.Stat_t{Mode: modeDevice, Nlink: 1}, 0
+}
+
+// IsDir implements the same method as documented on sys.File
+func (noopStdioFile) IsDir() (bool, experimentalsys.Errno) {
+ return false, 0
+}
+
+// Close implements the same method as documented on sys.File
+func (noopStdioFile) Close() (errno experimentalsys.Errno) { return }
+
+// IsNonblock implements the same method as documented on fsapi.File
+func (noopStdioFile) IsNonblock() bool {
+ return false
+}
+
+// SetNonblock implements the same method as documented on fsapi.File
+func (noopStdioFile) SetNonblock(bool) experimentalsys.Errno {
+ return experimentalsys.ENOSYS
+}
+
+// Poll implements the same method as documented on fsapi.File
+func (noopStdioFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) {
+ return false, experimentalsys.ENOSYS
+}
+
+func stdinFileEntry(r io.Reader) (*FileEntry, error) {
+ if r == nil {
+ return &FileEntry{Name: "stdin", IsPreopen: true, File: &noopStdinFile{}}, nil
+ } else if f, ok := r.(*os.File); ok {
+ if f, err := sysfs.NewStdioFile(true, f); err != nil {
+ return nil, err
+ } else {
+ return &FileEntry{Name: "stdin", IsPreopen: true, File: f}, nil
+ }
+ } else {
+ return &FileEntry{Name: "stdin", IsPreopen: true, File: &StdinFile{Reader: r}}, nil
+ }
+}
+
+func stdioWriterFileEntry(name string, w io.Writer) (*FileEntry, error) {
+ if w == nil {
+ return &FileEntry{Name: name, IsPreopen: true, File: &noopStdoutFile{}}, nil
+ } else if f, ok := w.(*os.File); ok {
+ if f, err := sysfs.NewStdioFile(false, f); err != nil {
+ return nil, err
+ } else {
+ return &FileEntry{Name: name, IsPreopen: true, File: f}, nil
+ }
+ } else {
+ return &FileEntry{Name: name, IsPreopen: true, File: &writerFile{w: w}}, nil
+ }
+}
diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go b/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go
new file mode 100644
index 000000000..12279ee49
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go
@@ -0,0 +1,228 @@
+package sys
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "time"
+
+ experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
+ "github.com/tetratelabs/wazero/internal/platform"
+ "github.com/tetratelabs/wazero/sys"
+)
+
+// Context holds module-scoped system resources currently only supported by
+// built-in host functions.
+type Context struct {
+ args, environ [][]byte
+ argsSize, environSize uint32
+
+ walltime sys.Walltime
+ walltimeResolution sys.ClockResolution
+ nanotime sys.Nanotime
+ nanotimeResolution sys.ClockResolution
+ nanosleep sys.Nanosleep
+ osyield sys.Osyield
+ randSource io.Reader
+ fsc FSContext
+}
+
+// Args is like os.Args and defaults to nil.
+//
+// Note: The count will never be more than math.MaxUint32.
+// See wazero.ModuleConfig WithArgs
+func (c *Context) Args() [][]byte {
+ return c.args
+}
+
+// ArgsSize is the size to encode Args as Null-terminated strings.
+//
+// Note: To get the size without null-terminators, subtract the length of Args from this value.
+// See wazero.ModuleConfig WithArgs
+// See https://en.wikipedia.org/wiki/Null-terminated_string
+func (c *Context) ArgsSize() uint32 {
+ return c.argsSize
+}
+
+// Environ are "key=value" entries like os.Environ and default to nil.
+//
+// Note: The count will never be more than math.MaxUint32.
+// See wazero.ModuleConfig WithEnv
+func (c *Context) Environ() [][]byte {
+ return c.environ
+}
+
+// EnvironSize is the size to encode Environ as Null-terminated strings.
+//
+// Note: To get the size without null-terminators, subtract the length of Environ from this value.
+// See wazero.ModuleConfig WithEnv
+// See https://en.wikipedia.org/wiki/Null-terminated_string
+func (c *Context) EnvironSize() uint32 {
+ return c.environSize
+}
+
+// Walltime implements platform.Walltime.
+func (c *Context) Walltime() (sec int64, nsec int32) {
+ return c.walltime()
+}
+
+// WalltimeNanos returns platform.Walltime as epoch nanoseconds.
+func (c *Context) WalltimeNanos() int64 {
+ sec, nsec := c.Walltime()
+ return (sec * time.Second.Nanoseconds()) + int64(nsec)
+}
+
+// WalltimeResolution returns resolution of Walltime.
+func (c *Context) WalltimeResolution() sys.ClockResolution {
+ return c.walltimeResolution
+}
+
+// Nanotime implements sys.Nanotime.
+func (c *Context) Nanotime() int64 {
+ return c.nanotime()
+}
+
+// NanotimeResolution returns resolution of Nanotime.
+func (c *Context) NanotimeResolution() sys.ClockResolution {
+ return c.nanotimeResolution
+}
+
+// Nanosleep implements sys.Nanosleep.
+func (c *Context) Nanosleep(ns int64) {
+ c.nanosleep(ns)
+}
+
+// Osyield implements sys.Osyield.
+func (c *Context) Osyield() {
+ c.osyield()
+}
+
+// FS returns the possibly empty (UnimplementedFS) file system context.
+func (c *Context) FS() *FSContext {
+ return &c.fsc
+}
+
+// RandSource is a source of random bytes and defaults to a deterministic source.
+// see wazero.ModuleConfig WithRandSource
+func (c *Context) RandSource() io.Reader {
+ return c.randSource
+}
+
+// DefaultContext returns Context with no values set except a possible nil
+// sys.FS.
+//
+// Note: This is only used for testing.
+func DefaultContext(fs experimentalsys.FS) *Context {
+ if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, []experimentalsys.FS{fs}, []string{""}, nil); err != nil {
+ panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
+ } else {
+ return sysCtx
+ }
+}
+
+// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
+// Note: max is exposed for testing. max is only used for env/args validation.
+func NewContext(
+ max uint32,
+ args, environ [][]byte,
+ stdin io.Reader,
+ stdout, stderr io.Writer,
+ randSource io.Reader,
+ walltime sys.Walltime,
+ walltimeResolution sys.ClockResolution,
+ nanotime sys.Nanotime,
+ nanotimeResolution sys.ClockResolution,
+ nanosleep sys.Nanosleep,
+ osyield sys.Osyield,
+ fs []experimentalsys.FS, guestPaths []string,
+ tcpListeners []*net.TCPListener,
+) (sysCtx *Context, err error) {
+ sysCtx = &Context{args: args, environ: environ}
+
+ if sysCtx.argsSize, err = nullTerminatedByteCount(max, args); err != nil {
+ return nil, fmt.Errorf("args invalid: %w", err)
+ }
+
+ if sysCtx.environSize, err = nullTerminatedByteCount(max, environ); err != nil {
+ return nil, fmt.Errorf("environ invalid: %w", err)
+ }
+
+ if randSource == nil {
+ sysCtx.randSource = platform.NewFakeRandSource()
+ } else {
+ sysCtx.randSource = randSource
+ }
+
+ if walltime != nil {
+ if clockResolutionInvalid(walltimeResolution) {
+ return nil, fmt.Errorf("invalid Walltime resolution: %d", walltimeResolution)
+ }
+ sysCtx.walltime = walltime
+ sysCtx.walltimeResolution = walltimeResolution
+ } else {
+ sysCtx.walltime = platform.NewFakeWalltime()
+ sysCtx.walltimeResolution = sys.ClockResolution(time.Microsecond.Nanoseconds())
+ }
+
+ if nanotime != nil {
+ if clockResolutionInvalid(nanotimeResolution) {
+ return nil, fmt.Errorf("invalid Nanotime resolution: %d", nanotimeResolution)
+ }
+ sysCtx.nanotime = nanotime
+ sysCtx.nanotimeResolution = nanotimeResolution
+ } else {
+ sysCtx.nanotime = platform.NewFakeNanotime()
+ sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond)
+ }
+
+ if nanosleep != nil {
+ sysCtx.nanosleep = nanosleep
+ } else {
+ sysCtx.nanosleep = platform.FakeNanosleep
+ }
+
+ if osyield != nil {
+ sysCtx.osyield = osyield
+ } else {
+ sysCtx.osyield = platform.FakeOsyield
+ }
+
+ err = sysCtx.InitFSContext(stdin, stdout, stderr, fs, guestPaths, tcpListeners)
+
+ return
+}
+
+// clockResolutionInvalid returns true if the value stored isn't reasonable.
+func clockResolutionInvalid(resolution sys.ClockResolution) bool {
+ return resolution < 1 || resolution > sys.ClockResolution(time.Hour.Nanoseconds())
+}
+
+// nullTerminatedByteCount ensures the count or Nul-terminated length of the elements doesn't exceed max, and that no
+// element includes the nul character.
+func nullTerminatedByteCount(max uint32, elements [][]byte) (uint32, error) {
+ count := uint32(len(elements))
+ if count > max {
+ return 0, errors.New("exceeds maximum count")
+ }
+
+ // The buffer size is the total size including null terminators. The null terminator count == value count, sum
+ // count with each value length. This works because in Go, the length of a string is the same as its byte count.
+ bufSize, maxSize := uint64(count), uint64(max) // uint64 to allow summing without overflow
+ for _, e := range elements {
+ // As this is null-terminated, We have to validate there are no null characters in the string.
+ for _, c := range e {
+ if c == 0 {
+ return 0, errors.New("contains NUL character")
+ }
+ }
+
+ nextSize := bufSize + uint64(len(e))
+ if nextSize > maxSize {
+ return 0, errors.New("exceeds maximum size")
+ }
+ bufSize = nextSize
+
+ }
+ return uint32(bufSize), nil
+}