github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/poll.go (about) 1 package wasi_snapshot_preview1 2 3 import ( 4 "context" 5 "syscall" 6 "time" 7 8 "github.com/tetratelabs/wazero/api" 9 internalsys "github.com/tetratelabs/wazero/internal/sys" 10 "github.com/tetratelabs/wazero/internal/wasip1" 11 "github.com/tetratelabs/wazero/internal/wasm" 12 ) 13 14 // pollOneoff is the WASI function named PollOneoffName that concurrently 15 // polls for the occurrence of a set of events. 16 // 17 // # Parameters 18 // 19 // - in: pointer to the subscriptions (48 bytes each) 20 // - out: pointer to the resulting events (32 bytes each) 21 // - nsubscriptions: count of subscriptions, zero returns syscall.EINVAL. 22 // - resultNevents: count of events. 23 // 24 // Result (Errno) 25 // 26 // The return value is 0 except the following error conditions: 27 // - syscall.EINVAL: the parameters are invalid 28 // - syscall.ENOTSUP: a parameters is valid, but not yet supported. 29 // - syscall.EFAULT: there is not enough memory to read the subscriptions or 30 // write results. 31 // 32 // # Notes 33 // 34 // - Since the `out` pointer nests Errno, the result is always 0. 35 // - This is similar to `poll` in POSIX. 36 // 37 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff 38 // See https://linux.die.net/man/3/poll 39 var pollOneoff = newHostFunc( 40 wasip1.PollOneoffName, pollOneoffFn, 41 []api.ValueType{i32, i32, i32, i32}, 42 "in", "out", "nsubscriptions", "result.nevents", 43 ) 44 45 type event struct { 46 eventType byte 47 userData []byte 48 errno wasip1.Errno 49 outOffset uint32 50 } 51 52 func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 53 in := uint32(params[0]) 54 out := uint32(params[1]) 55 nsubscriptions := uint32(params[2]) 56 resultNevents := uint32(params[3]) 57 58 if nsubscriptions == 0 { 59 return syscall.EINVAL 60 } 61 62 mem := mod.Memory() 63 64 // Ensure capacity prior to the read loop to reduce error handling. 65 inBuf, ok := mem.Read(in, nsubscriptions*48) 66 if !ok { 67 return syscall.EFAULT 68 } 69 outBuf, ok := mem.Read(out, nsubscriptions*32) 70 // zero-out all buffer before writing 71 for i := range outBuf { 72 outBuf[i] = 0 73 } 74 75 if !ok { 76 return syscall.EFAULT 77 } 78 79 // Eagerly write the number of events which will equal subscriptions unless 80 // there's a fault in parsing (not processing). 81 if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) { 82 return syscall.EFAULT 83 } 84 85 // Loop through all subscriptions and write their output. 86 87 // Extract FS context, used in the body of the for loop for FS access. 88 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 89 // Slice of events that are processed out of the loop (stdin subscribers). 90 var stdinSubs []*event 91 // The timeout is initialized at max Duration, the loop will find the minimum. 92 var timeout time.Duration = 1<<63 - 1 93 // Count of all the clock subscribers that have been already written back to outBuf. 94 clockEvents := uint32(0) 95 // Count of all the non-clock subscribers that have been already written back to outBuf. 96 readySubs := uint32(0) 97 98 // Layout is subscription_u: Union 99 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u 100 for i := uint32(0); i < nsubscriptions; i++ { 101 inOffset := i * 48 102 outOffset := i * 32 103 104 eventType := inBuf[inOffset+8] // +8 past userdata 105 // +8 past userdata +8 contents_offset 106 argBuf := inBuf[inOffset+8+8:] 107 userData := inBuf[inOffset : inOffset+8] 108 109 evt := &event{ 110 eventType: eventType, 111 userData: userData, 112 errno: wasip1.ErrnoSuccess, 113 outOffset: outOffset, 114 } 115 116 switch eventType { 117 case wasip1.EventTypeClock: // handle later 118 clockEvents++ 119 newTimeout, err := processClockEvent(argBuf) 120 if err != 0 { 121 return err 122 } 123 // Min timeout. 124 if newTimeout < timeout { 125 timeout = newTimeout 126 } 127 // Ack the clock event to the outBuf. 128 writeEvent(outBuf, evt) 129 case wasip1.EventTypeFdRead: 130 fd := int32(le.Uint32(argBuf)) 131 if fd < 0 { 132 return syscall.EBADF 133 } 134 if fd == internalsys.FdStdin { 135 // if the fd is Stdin, do not ack yet, 136 // append to a slice for delayed evaluation. 137 stdinSubs = append(stdinSubs, evt) 138 } else { 139 evt.errno = processFDEventRead(fsc, fd) 140 writeEvent(outBuf, evt) 141 readySubs++ 142 } 143 case wasip1.EventTypeFdWrite: 144 fd := int32(le.Uint32(argBuf)) 145 if fd < 0 { 146 return syscall.EBADF 147 } 148 evt.errno = processFDEventWrite(fsc, fd) 149 readySubs++ 150 writeEvent(outBuf, evt) 151 default: 152 return syscall.EINVAL 153 } 154 } 155 156 // If there are subscribers with data ready, we have already written them to outBuf, 157 // and we don't need to wait for the timeout: clear it. 158 if readySubs != 0 { 159 timeout = 0 160 } 161 162 // If there are stdin subscribers, check for data with given timeout. 163 if len(stdinSubs) > 0 { 164 stdin, ok := fsc.LookupFile(internalsys.FdStdin) 165 if !ok { 166 return syscall.EBADF 167 } 168 // Wait for the timeout to expire, or for some data to become available on Stdin. 169 stdinReady, errno := stdin.File.PollRead(&timeout) 170 if errno != 0 { 171 return errno 172 } 173 if stdinReady { 174 // stdin has data ready to for reading, write back all the events 175 for i := range stdinSubs { 176 readySubs++ 177 evt := stdinSubs[i] 178 evt.errno = 0 179 writeEvent(outBuf, evt) 180 } 181 } 182 } else { 183 // No subscribers, just wait for the given timeout. 184 sysCtx := mod.(*wasm.ModuleInstance).Sys 185 sysCtx.Nanosleep(int64(timeout)) 186 } 187 188 if readySubs != nsubscriptions { 189 if !mod.Memory().WriteUint32Le(resultNevents, readySubs+clockEvents) { 190 return syscall.EFAULT 191 } 192 } 193 194 return 0 195 } 196 197 // processClockEvent supports only relative name events, as that's what's used 198 // to implement sleep in various compilers including Rust, Zig and TinyGo. 199 func processClockEvent(inBuf []byte) (time.Duration, syscall.Errno) { 200 _ /* ID */ = le.Uint32(inBuf[0:8]) // See below 201 timeout := le.Uint64(inBuf[8:16]) // nanos if relative 202 _ /* precision */ = le.Uint64(inBuf[16:24]) // Unused 203 flags := le.Uint16(inBuf[24:32]) 204 205 var err syscall.Errno 206 // subclockflags has only one flag defined: subscription_clock_abstime 207 switch flags { 208 case 0: // relative time 209 case 1: // subscription_clock_abstime 210 err = syscall.ENOTSUP 211 default: // subclockflags has only one flag defined. 212 err = syscall.EINVAL 213 } 214 215 if err != 0 { 216 return 0, err 217 } else { 218 // https://linux.die.net/man/3/clock_settime says relative timers are 219 // unaffected. Since this function only supports relative timeout, we can 220 // skip name ID validation and use a single sleep function. 221 222 return time.Duration(timeout), 0 223 } 224 } 225 226 // processFDEventRead returns ErrnoSuccess if the file exists and ErrnoBadf otherwise. 227 func processFDEventRead(fsc *internalsys.FSContext, fd int32) wasip1.Errno { 228 if _, ok := fsc.LookupFile(fd); ok { 229 return wasip1.ErrnoSuccess 230 } else { 231 return wasip1.ErrnoBadf 232 } 233 } 234 235 // processFDEventWrite returns ErrnoNotsup if the file exists and ErrnoBadf otherwise. 236 func processFDEventWrite(fsc *internalsys.FSContext, fd int32) wasip1.Errno { 237 if _, ok := fsc.LookupFile(fd); ok { 238 return wasip1.ErrnoNotsup 239 } 240 return wasip1.ErrnoBadf 241 } 242 243 // writeEvent writes the event corresponding to the processed subscription. 244 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct 245 func writeEvent(outBuf []byte, evt *event) { 246 copy(outBuf[evt.outOffset:], evt.userData) // userdata 247 outBuf[evt.outOffset+8] = byte(evt.errno) // uint16, but safe as < 255 248 outBuf[evt.outOffset+9] = 0 249 le.PutUint32(outBuf[evt.outOffset+10:], uint32(evt.eventType)) 250 // TODO: When FD events are supported, write outOffset+16 251 }