mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02: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:
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
|
||||
}
|
Reference in New Issue
Block a user