mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 09:32:25 -05:00 
			
		
		
		
	[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/).
This commit is contained in:
		
					parent
					
						
							
								cce21c11cb
							
						
					
				
			
			
				commit
				
					
						1e7b32490d
					
				
			
		
					 398 changed files with 86174 additions and 684 deletions
				
			
		
							
								
								
									
										457
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/fs.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										457
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/fs.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -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] | ||||
| } | ||||
							
								
								
									
										151
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -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 | ||||
| } | ||||
							
								
								
									
										128
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										228
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/sys.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								vendor/github.com/tetratelabs/wazero/internal/sys/sys.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -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 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue