mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-24 20:56:39 +00:00
225 lines
5.6 KiB
Go
225 lines
5.6 KiB
Go
|
package sysfs
|
||
|
|
||
|
import (
|
||
|
"syscall"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/tetratelabs/wazero/experimental/sys"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
procWSAPoll = modws2_32.NewProc("WSAPoll")
|
||
|
procGetNamedPipeInfo = kernel32.NewProc("GetNamedPipeInfo")
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// _POLLRDNORM subscribes to normal data for read.
|
||
|
_POLLRDNORM = 0x0100
|
||
|
// _POLLRDBAND subscribes to priority band (out-of-band) data for read.
|
||
|
_POLLRDBAND = 0x0200
|
||
|
// _POLLIN subscribes a notification when any readable data is available.
|
||
|
_POLLIN = (_POLLRDNORM | _POLLRDBAND)
|
||
|
)
|
||
|
|
||
|
// pollFd is the struct to query for file descriptor events using poll.
|
||
|
type pollFd struct {
|
||
|
// fd is the file descriptor.
|
||
|
fd uintptr
|
||
|
// events is a bitmap containing the requested events.
|
||
|
events int16
|
||
|
// revents is a bitmap containing the returned events.
|
||
|
revents int16
|
||
|
}
|
||
|
|
||
|
// newPollFd is a constructor for pollFd that abstracts the platform-specific type of file descriptors.
|
||
|
func newPollFd(fd uintptr, events, revents int16) pollFd {
|
||
|
return pollFd{fd: fd, events: events, revents: revents}
|
||
|
}
|
||
|
|
||
|
// pollInterval is the interval between each calls to peekNamedPipe in selectAllHandles
|
||
|
const pollInterval = 100 * time.Millisecond
|
||
|
|
||
|
// _poll implements poll on Windows, for a subset of cases.
|
||
|
//
|
||
|
// fds may contain any number of file handles, but regular files and pipes are only processed for _POLLIN.
|
||
|
// Stdin is a pipe, thus it is checked for readiness when present. Pipes are checked using PeekNamedPipe.
|
||
|
// Regular files always immediately reported as ready, regardless their actual state and timeouts.
|
||
|
//
|
||
|
// If n==0 it will wait for the given timeout duration, but it will return sys.ENOSYS if timeout is nil,
|
||
|
// i.e. it won't block indefinitely. The given ctx is used to allow for cancellation,
|
||
|
// and it is currently used only in tests.
|
||
|
//
|
||
|
// The implementation actually polls every 100 milliseconds (pollInterval) until it reaches the
|
||
|
// given timeout (in millis).
|
||
|
//
|
||
|
// The duration may be negative, in which case it will wait indefinitely. The given ctx is
|
||
|
// used to allow for cancellation, and it is currently used only in tests.
|
||
|
func _poll(fds []pollFd, timeoutMillis int32) (n int, errno sys.Errno) {
|
||
|
if fds == nil {
|
||
|
return -1, sys.ENOSYS
|
||
|
}
|
||
|
|
||
|
regular, pipes, sockets, errno := partionByFtype(fds)
|
||
|
nregular := len(regular)
|
||
|
if errno != 0 {
|
||
|
return -1, errno
|
||
|
}
|
||
|
|
||
|
// Ticker that emits at every pollInterval.
|
||
|
tick := time.NewTicker(pollInterval)
|
||
|
tickCh := tick.C
|
||
|
defer tick.Stop()
|
||
|
|
||
|
// Timer that expires after the given duration.
|
||
|
// Initialize afterCh as nil: the select below will wait forever.
|
||
|
var afterCh <-chan time.Time
|
||
|
if timeoutMillis >= 0 {
|
||
|
// If duration is not nil, instantiate the timer.
|
||
|
after := time.NewTimer(time.Duration(timeoutMillis) * time.Millisecond)
|
||
|
defer after.Stop()
|
||
|
afterCh = after.C
|
||
|
}
|
||
|
|
||
|
npipes, nsockets, errno := peekAll(pipes, sockets)
|
||
|
if errno != 0 {
|
||
|
return -1, errno
|
||
|
}
|
||
|
count := nregular + npipes + nsockets
|
||
|
if count > 0 {
|
||
|
return count, 0
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case <-afterCh:
|
||
|
return 0, 0
|
||
|
case <-tickCh:
|
||
|
npipes, nsockets, errno := peekAll(pipes, sockets)
|
||
|
if errno != 0 {
|
||
|
return -1, errno
|
||
|
}
|
||
|
count = nregular + npipes + nsockets
|
||
|
if count > 0 {
|
||
|
return count, 0
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func peekAll(pipes, sockets []pollFd) (npipes, nsockets int, errno sys.Errno) {
|
||
|
npipes, errno = peekPipes(pipes)
|
||
|
if errno != 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Invoke wsaPoll with a 0-timeout to avoid blocking.
|
||
|
// Timeouts are handled in pollWithContext instead.
|
||
|
nsockets, errno = wsaPoll(sockets, 0)
|
||
|
if errno != 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
count := npipes + nsockets
|
||
|
if count > 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func peekPipes(fds []pollFd) (n int, errno sys.Errno) {
|
||
|
for _, fd := range fds {
|
||
|
bytes, errno := peekNamedPipe(syscall.Handle(fd.fd))
|
||
|
if errno != 0 {
|
||
|
return -1, sys.UnwrapOSError(errno)
|
||
|
}
|
||
|
if bytes > 0 {
|
||
|
n++
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// wsaPoll is the WSAPoll function from winsock2.
|
||
|
//
|
||
|
// See https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll
|
||
|
func wsaPoll(fds []pollFd, timeout int) (n int, errno sys.Errno) {
|
||
|
if len(fds) > 0 {
|
||
|
sockptr := &fds[0]
|
||
|
ns, _, e := syscall.SyscallN(
|
||
|
procWSAPoll.Addr(),
|
||
|
uintptr(unsafe.Pointer(sockptr)),
|
||
|
uintptr(len(fds)),
|
||
|
uintptr(timeout))
|
||
|
if e != 0 {
|
||
|
return -1, sys.UnwrapOSError(e)
|
||
|
}
|
||
|
n = int(ns)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// ftype is a type of file that can be handled by poll.
|
||
|
type ftype uint8
|
||
|
|
||
|
const (
|
||
|
ftype_regular ftype = iota
|
||
|
ftype_pipe
|
||
|
ftype_socket
|
||
|
)
|
||
|
|
||
|
// partionByFtype checks the type of each fd in fds and returns 3 distinct partitions
|
||
|
// for regular files, named pipes and sockets.
|
||
|
func partionByFtype(fds []pollFd) (regular, pipe, socket []pollFd, errno sys.Errno) {
|
||
|
for _, pfd := range fds {
|
||
|
t, errno := ftypeOf(pfd.fd)
|
||
|
if errno != 0 {
|
||
|
return nil, nil, nil, errno
|
||
|
}
|
||
|
switch t {
|
||
|
case ftype_regular:
|
||
|
regular = append(regular, pfd)
|
||
|
case ftype_pipe:
|
||
|
pipe = append(pipe, pfd)
|
||
|
case ftype_socket:
|
||
|
socket = append(socket, pfd)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// ftypeOf checks the type of fd and return the corresponding ftype.
|
||
|
func ftypeOf(fd uintptr) (ftype, sys.Errno) {
|
||
|
h := syscall.Handle(fd)
|
||
|
t, err := syscall.GetFileType(h)
|
||
|
if err != nil {
|
||
|
return 0, sys.UnwrapOSError(err)
|
||
|
}
|
||
|
switch t {
|
||
|
case syscall.FILE_TYPE_CHAR, syscall.FILE_TYPE_DISK:
|
||
|
return ftype_regular, 0
|
||
|
case syscall.FILE_TYPE_PIPE:
|
||
|
if isSocket(h) {
|
||
|
return ftype_socket, 0
|
||
|
} else {
|
||
|
return ftype_pipe, 0
|
||
|
}
|
||
|
default:
|
||
|
return ftype_regular, 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// isSocket returns true if the given file handle
|
||
|
// is a pipe.
|
||
|
func isSocket(fd syscall.Handle) bool {
|
||
|
r, _, errno := syscall.SyscallN(
|
||
|
procGetNamedPipeInfo.Addr(),
|
||
|
uintptr(fd),
|
||
|
uintptr(unsafe.Pointer(nil)),
|
||
|
uintptr(unsafe.Pointer(nil)),
|
||
|
uintptr(unsafe.Pointer(nil)),
|
||
|
uintptr(unsafe.Pointer(nil)))
|
||
|
return r == 0 || errno != 0
|
||
|
}
|