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