mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 16:12:24 -05:00 
			
		
		
		
	This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
		
			
				
	
	
		
			228 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | |
| }
 |