1package sysfs
2
3import (
4 "syscall"
5 "time"
6 "unsafe"
7
8 "github.com/tetratelabs/wazero/experimental/sys"
9)
10
11var (
12 procWSAPoll = modws2_32.NewProc("WSAPoll")
13 procGetNamedPipeInfo = kernel32.NewProc("GetNamedPipeInfo")
14)
15
16const (
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.
26type 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.
36func 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
41const 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.
58func _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
110func 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
131func 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
147func 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.
164type ftype uint8
165
166const (
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.
174func 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.
193func 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.
215func 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}