github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sys/sys.go (about) 1 package sys 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net" 8 "time" 9 10 experimentalsys "github.com/wasilibs/wazerox/experimental/sys" 11 "github.com/wasilibs/wazerox/internal/platform" 12 "github.com/wasilibs/wazerox/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 [][]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 35 func (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 44 func (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 52 func (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 61 func (c *Context) EnvironSize() uint32 { 62 return c.environSize 63 } 64 65 // Walltime implements platform.Walltime. 66 func (c *Context) Walltime() (sec int64, nsec int32) { 67 return c.walltime() 68 } 69 70 // WalltimeNanos returns platform.Walltime as epoch nanoseconds. 71 func (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. 77 func (c *Context) WalltimeResolution() sys.ClockResolution { 78 return c.walltimeResolution 79 } 80 81 // Nanotime implements sys.Nanotime. 82 func (c *Context) Nanotime() int64 { 83 return c.nanotime() 84 } 85 86 // NanotimeResolution returns resolution of Nanotime. 87 func (c *Context) NanotimeResolution() sys.ClockResolution { 88 return c.nanotimeResolution 89 } 90 91 // Nanosleep implements sys.Nanosleep. 92 func (c *Context) Nanosleep(ns int64) { 93 c.nanosleep(ns) 94 } 95 96 // Osyield implements sys.Osyield. 97 func (c *Context) Osyield() { 98 c.osyield() 99 } 100 101 // FS returns the possibly empty (UnimplementedFS) file system context. 102 func (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 108 func (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. 116 func 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. 126 func 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. 197 func 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. 203 func 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 }