sys.go

  1package sys
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7	"net"
  8	"time"
  9
 10	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
 11	"github.com/tetratelabs/wazero/internal/platform"
 12	"github.com/tetratelabs/wazero/sys"
 13)
 14
 15// Context holds module-scoped system resources currently only supported by
 16// built-in host functions.
 17type Context struct {
 18	args, environ         [][]byte
 19	argsSize, environSize uint32
 20
 21	walltime           sys.Walltime
 22	walltimeResolution sys.ClockResolution
 23	nanotime           sys.Nanotime
 24	nanotimeResolution sys.ClockResolution
 25	nanosleep          sys.Nanosleep
 26	osyield            sys.Osyield
 27	randSource         io.Reader
 28	fsc                FSContext
 29}
 30
 31// Args is like os.Args and defaults to nil.
 32//
 33// Note: The count will never be more than math.MaxUint32.
 34// See wazero.ModuleConfig WithArgs
 35func (c *Context) Args() [][]byte {
 36	return c.args
 37}
 38
 39// ArgsSize is the size to encode Args as Null-terminated strings.
 40//
 41// Note: To get the size without null-terminators, subtract the length of Args from this value.
 42// See wazero.ModuleConfig WithArgs
 43// See https://en.wikipedia.org/wiki/Null-terminated_string
 44func (c *Context) ArgsSize() uint32 {
 45	return c.argsSize
 46}
 47
 48// Environ are "key=value" entries like os.Environ and default to nil.
 49//
 50// Note: The count will never be more than math.MaxUint32.
 51// See wazero.ModuleConfig WithEnv
 52func (c *Context) Environ() [][]byte {
 53	return c.environ
 54}
 55
 56// EnvironSize is the size to encode Environ as Null-terminated strings.
 57//
 58// Note: To get the size without null-terminators, subtract the length of Environ from this value.
 59// See wazero.ModuleConfig WithEnv
 60// See https://en.wikipedia.org/wiki/Null-terminated_string
 61func (c *Context) EnvironSize() uint32 {
 62	return c.environSize
 63}
 64
 65// Walltime implements platform.Walltime.
 66func (c *Context) Walltime() (sec int64, nsec int32) {
 67	return c.walltime()
 68}
 69
 70// WalltimeNanos returns platform.Walltime as epoch nanoseconds.
 71func (c *Context) WalltimeNanos() int64 {
 72	sec, nsec := c.Walltime()
 73	return (sec * time.Second.Nanoseconds()) + int64(nsec)
 74}
 75
 76// WalltimeResolution returns resolution of Walltime.
 77func (c *Context) WalltimeResolution() sys.ClockResolution {
 78	return c.walltimeResolution
 79}
 80
 81// Nanotime implements sys.Nanotime.
 82func (c *Context) Nanotime() int64 {
 83	return c.nanotime()
 84}
 85
 86// NanotimeResolution returns resolution of Nanotime.
 87func (c *Context) NanotimeResolution() sys.ClockResolution {
 88	return c.nanotimeResolution
 89}
 90
 91// Nanosleep implements sys.Nanosleep.
 92func (c *Context) Nanosleep(ns int64) {
 93	c.nanosleep(ns)
 94}
 95
 96// Osyield implements sys.Osyield.
 97func (c *Context) Osyield() {
 98	c.osyield()
 99}
100
101// FS returns the possibly empty (UnimplementedFS) file system context.
102func (c *Context) FS() *FSContext {
103	return &c.fsc
104}
105
106// RandSource is a source of random bytes and defaults to a deterministic source.
107// see wazero.ModuleConfig WithRandSource
108func (c *Context) RandSource() io.Reader {
109	return c.randSource
110}
111
112// DefaultContext returns Context with no values set except a possible nil
113// sys.FS.
114//
115// Note: This is only used for testing.
116func DefaultContext(fs experimentalsys.FS) *Context {
117	if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, []experimentalsys.FS{fs}, []string{""}, nil); err != nil {
118		panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
119	} else {
120		return sysCtx
121	}
122}
123
124// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
125// Note: max is exposed for testing. max is only used for env/args validation.
126func NewContext(
127	max uint32,
128	args, environ [][]byte,
129	stdin io.Reader,
130	stdout, stderr io.Writer,
131	randSource io.Reader,
132	walltime sys.Walltime,
133	walltimeResolution sys.ClockResolution,
134	nanotime sys.Nanotime,
135	nanotimeResolution sys.ClockResolution,
136	nanosleep sys.Nanosleep,
137	osyield sys.Osyield,
138	fs []experimentalsys.FS, guestPaths []string,
139	tcpListeners []*net.TCPListener,
140) (sysCtx *Context, err error) {
141	sysCtx = &Context{args: args, environ: environ}
142
143	if sysCtx.argsSize, err = nullTerminatedByteCount(max, args); err != nil {
144		return nil, fmt.Errorf("args invalid: %w", err)
145	}
146
147	if sysCtx.environSize, err = nullTerminatedByteCount(max, environ); err != nil {
148		return nil, fmt.Errorf("environ invalid: %w", err)
149	}
150
151	if randSource == nil {
152		sysCtx.randSource = platform.NewFakeRandSource()
153	} else {
154		sysCtx.randSource = randSource
155	}
156
157	if walltime != nil {
158		if clockResolutionInvalid(walltimeResolution) {
159			return nil, fmt.Errorf("invalid Walltime resolution: %d", walltimeResolution)
160		}
161		sysCtx.walltime = walltime
162		sysCtx.walltimeResolution = walltimeResolution
163	} else {
164		sysCtx.walltime = platform.NewFakeWalltime()
165		sysCtx.walltimeResolution = sys.ClockResolution(time.Microsecond.Nanoseconds())
166	}
167
168	if nanotime != nil {
169		if clockResolutionInvalid(nanotimeResolution) {
170			return nil, fmt.Errorf("invalid Nanotime resolution: %d", nanotimeResolution)
171		}
172		sysCtx.nanotime = nanotime
173		sysCtx.nanotimeResolution = nanotimeResolution
174	} else {
175		sysCtx.nanotime = platform.NewFakeNanotime()
176		sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond)
177	}
178
179	if nanosleep != nil {
180		sysCtx.nanosleep = nanosleep
181	} else {
182		sysCtx.nanosleep = platform.FakeNanosleep
183	}
184
185	if osyield != nil {
186		sysCtx.osyield = osyield
187	} else {
188		sysCtx.osyield = platform.FakeOsyield
189	}
190
191	err = sysCtx.InitFSContext(stdin, stdout, stderr, fs, guestPaths, tcpListeners)
192
193	return
194}
195
196// clockResolutionInvalid returns true if the value stored isn't reasonable.
197func clockResolutionInvalid(resolution sys.ClockResolution) bool {
198	return resolution < 1 || resolution > sys.ClockResolution(time.Hour.Nanoseconds())
199}
200
201// nullTerminatedByteCount ensures the count or Nul-terminated length of the elements doesn't exceed max, and that no
202// element includes the nul character.
203func nullTerminatedByteCount(max uint32, elements [][]byte) (uint32, error) {
204	count := uint32(len(elements))
205	if count > max {
206		return 0, errors.New("exceeds maximum count")
207	}
208
209	// The buffer size is the total size including null terminators. The null terminator count == value count, sum
210	// count with each value length. This works because in Go, the length of a string is the same as its byte count.
211	bufSize, maxSize := uint64(count), uint64(max) // uint64 to allow summing without overflow
212	for _, e := range elements {
213		// As this is null-terminated, We have to validate there are no null characters in the string.
214		for _, c := range e {
215			if c == 0 {
216				return 0, errors.New("contains NUL character")
217			}
218		}
219
220		nextSize := bufSize + uint64(len(e))
221		if nextSize > maxSize {
222			return 0, errors.New("exceeds maximum size")
223		}
224		bufSize = nextSize
225
226	}
227	return uint32(bufSize), nil
228}