diff options
| author | 2024-05-27 15:46:15 +0000 | |
|---|---|---|
| committer | 2024-05-27 17:46:15 +0200 | |
| commit | 1e7b32490dfdccddd04f46d4b0416b48d749d51b (patch) | |
| tree | 62a11365933a5a11e0800af64cbdf9172e5e6e7a /vendor/github.com/tetratelabs/wazero/internal/sys | |
| parent | [chore] Small styling + link issues (#2933) (diff) | |
| download | gotosocial-1e7b32490dfdccddd04f46d4b0416b48d749d51b.tar.xz | |
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/internal/sys')
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 +} | 
