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}