github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sysfs/poll_windows.go (about) 1 package sysfs 2 3 import ( 4 "syscall" 5 "time" 6 "unsafe" 7 8 "github.com/wasilibs/wazerox/experimental/sys" 9 ) 10 11 var ( 12 procWSAPoll = modws2_32.NewProc("WSAPoll") 13 procGetNamedPipeInfo = kernel32.NewProc("GetNamedPipeInfo") 14 ) 15 16 const ( 17 // _POLLRDNORM subscribes to normal data for read. 18 _POLLRDNORM = 0x0100 19 // _POLLRDBAND subscribes to priority band (out-of-band) data for read. 20 _POLLRDBAND = 0x0200 21 // _POLLIN subscribes a notification when any readable data is available. 22 _POLLIN = (_POLLRDNORM | _POLLRDBAND) 23 ) 24 25 // pollFd is the struct to query for file descriptor events using poll. 26 type pollFd struct { 27 // fd is the file descriptor. 28 fd uintptr 29 // events is a bitmap containing the requested events. 30 events int16 31 // revents is a bitmap containing the returned events. 32 revents int16 33 } 34 35 // newPollFd is a constructor for pollFd that abstracts the platform-specific type of file descriptors. 36 func newPollFd(fd uintptr, events, revents int16) pollFd { 37 return pollFd{fd: fd, events: events, revents: revents} 38 } 39 40 // pollInterval is the interval between each calls to peekNamedPipe in selectAllHandles 41 const pollInterval = 100 * time.Millisecond 42 43 // _poll implements poll on Windows, for a subset of cases. 44 // 45 // fds may contain any number of file handles, but regular files and pipes are only processed for _POLLIN. 46 // Stdin is a pipe, thus it is checked for readiness when present. Pipes are checked using PeekNamedPipe. 47 // Regular files always immediately reported as ready, regardless their actual state and timeouts. 48 // 49 // If n==0 it will wait for the given timeout duration, but it will return sys.ENOSYS if timeout is nil, 50 // i.e. it won't block indefinitely. The given ctx is used to allow for cancellation, 51 // and it is currently used only in tests. 52 // 53 // The implementation actually polls every 100 milliseconds (pollInterval) until it reaches the 54 // given timeout (in millis). 55 // 56 // The duration may be negative, in which case it will wait indefinitely. The given ctx is 57 // used to allow for cancellation, and it is currently used only in tests. 58 func _poll(fds []pollFd, timeoutMillis int32) (n int, errno sys.Errno) { 59 if fds == nil { 60 return -1, sys.ENOSYS 61 } 62 63 regular, pipes, sockets, errno := partionByFtype(fds) 64 nregular := len(regular) 65 if errno != 0 { 66 return -1, errno 67 } 68 69 // Ticker that emits at every pollInterval. 70 tick := time.NewTicker(pollInterval) 71 tickCh := tick.C 72 defer tick.Stop() 73 74 // Timer that expires after the given duration. 75 // Initialize afterCh as nil: the select below will wait forever. 76 var afterCh <-chan time.Time 77 if timeoutMillis >= 0 { 78 // If duration is not nil, instantiate the timer. 79 after := time.NewTimer(time.Duration(timeoutMillis) * time.Millisecond) 80 defer after.Stop() 81 afterCh = after.C 82 } 83 84 npipes, nsockets, errno := peekAll(pipes, sockets) 85 if errno != 0 { 86 return -1, errno 87 } 88 count := nregular + npipes + nsockets 89 if count > 0 { 90 return count, 0 91 } 92 93 for { 94 select { 95 case <-afterCh: 96 return 0, 0 97 case <-tickCh: 98 npipes, nsockets, errno := peekAll(pipes, sockets) 99 if errno != 0 { 100 return -1, errno 101 } 102 count = nregular + npipes + nsockets 103 if count > 0 { 104 return count, 0 105 } 106 } 107 } 108 } 109 110 func peekAll(pipes, sockets []pollFd) (npipes, nsockets int, errno sys.Errno) { 111 npipes, errno = peekPipes(pipes) 112 if errno != 0 { 113 return 114 } 115 116 // Invoke wsaPoll with a 0-timeout to avoid blocking. 117 // Timeouts are handled in pollWithContext instead. 118 nsockets, errno = wsaPoll(sockets, 0) 119 if errno != 0 { 120 return 121 } 122 123 count := npipes + nsockets 124 if count > 0 { 125 return 126 } 127 128 return 129 } 130 131 func peekPipes(fds []pollFd) (n int, errno sys.Errno) { 132 for _, fd := range fds { 133 bytes, errno := peekNamedPipe(syscall.Handle(fd.fd)) 134 if errno != 0 { 135 return -1, sys.UnwrapOSError(errno) 136 } 137 if bytes > 0 { 138 n++ 139 } 140 } 141 return 142 } 143 144 // wsaPoll is the WSAPoll function from winsock2. 145 // 146 // See https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll 147 func wsaPoll(fds []pollFd, timeout int) (n int, errno sys.Errno) { 148 if len(fds) > 0 { 149 sockptr := &fds[0] 150 ns, _, e := syscall.SyscallN( 151 procWSAPoll.Addr(), 152 uintptr(unsafe.Pointer(sockptr)), 153 uintptr(len(fds)), 154 uintptr(timeout)) 155 if e != 0 { 156 return -1, sys.UnwrapOSError(e) 157 } 158 n = int(ns) 159 } 160 return 161 } 162 163 // ftype is a type of file that can be handled by poll. 164 type ftype uint8 165 166 const ( 167 ftype_regular ftype = iota 168 ftype_pipe 169 ftype_socket 170 ) 171 172 // partionByFtype checks the type of each fd in fds and returns 3 distinct partitions 173 // for regular files, named pipes and sockets. 174 func partionByFtype(fds []pollFd) (regular, pipe, socket []pollFd, errno sys.Errno) { 175 for _, pfd := range fds { 176 t, errno := ftypeOf(pfd.fd) 177 if errno != 0 { 178 return nil, nil, nil, errno 179 } 180 switch t { 181 case ftype_regular: 182 regular = append(regular, pfd) 183 case ftype_pipe: 184 pipe = append(pipe, pfd) 185 case ftype_socket: 186 socket = append(socket, pfd) 187 } 188 } 189 return 190 } 191 192 // ftypeOf checks the type of fd and return the corresponding ftype. 193 func ftypeOf(fd uintptr) (ftype, sys.Errno) { 194 h := syscall.Handle(fd) 195 t, err := syscall.GetFileType(h) 196 if err != nil { 197 return 0, sys.UnwrapOSError(err) 198 } 199 switch t { 200 case syscall.FILE_TYPE_CHAR, syscall.FILE_TYPE_DISK: 201 return ftype_regular, 0 202 case syscall.FILE_TYPE_PIPE: 203 if isSocket(h) { 204 return ftype_socket, 0 205 } else { 206 return ftype_pipe, 0 207 } 208 default: 209 return ftype_regular, 0 210 } 211 } 212 213 // isSocket returns true if the given file handle 214 // is a pipe. 215 func isSocket(fd syscall.Handle) bool { 216 r, _, errno := syscall.SyscallN( 217 procGetNamedPipeInfo.Addr(), 218 uintptr(fd), 219 uintptr(unsafe.Pointer(nil)), 220 uintptr(unsafe.Pointer(nil)), 221 uintptr(unsafe.Pointer(nil)), 222 uintptr(unsafe.Pointer(nil))) 223 return r == 0 || errno != 0 224 }