diff options
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/internal/sys')
4 files changed, 0 insertions, 964 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go b/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go deleted file mode 100644 index 157de788f..000000000 --- a/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go +++ /dev/null @@ -1,457 +0,0 @@ -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 deleted file mode 100644 index fe233d29e..000000000 --- a/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go +++ /dev/null @@ -1,151 +0,0 @@ -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 deleted file mode 100644 index 32c33661e..000000000 --- a/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go +++ /dev/null @@ -1,128 +0,0 @@ -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 deleted file mode 100644 index 12279ee49..000000000 --- a/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go +++ /dev/null @@ -1,228 +0,0 @@ -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 -} |