okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
04a6149
main
1//go:build darwin || freebsd || netbsd || openbsd || dragonfly 2// +build darwin freebsd netbsd openbsd dragonfly 3 4package uv 5 6import ( 7"errors" 8"fmt" 9"io" 10"os" 11"strings" 12"sync" 13"time" 14 15"golang.org/x/sys/unix" 16) 17 18// newPollReader creates a new pollReader for the given io.Reader. 19func newPollReader (reader io.Reader ) (pollReader ,error ) { 20file ,ok := reader .(File ) 21if ! ok { 22return newFallbackReader (reader ) 23 } 24 25// kqueue returns instantly when polling /dev/tty so fallback to select 26if file .Name ()== "/dev/tty" { 27return newSelectPollReader (reader ) 28 } 29 30kQueue ,err := unix .Kqueue () 31if err != nil { 32return nil ,fmt .Errorf ("create kqueue: %w" ,err ) 33 } 34 35r := & kqueueReader { 36reader :reader , 37file :file , 38kQueue :kQueue , 39 } 40 41r .cancelSignalReader ,r .cancelSignalWriter ,err = os .Pipe () 42if err != nil { 43_ = unix .Close (kQueue ) 44return nil ,err 45 } 46 47unix .SetKevent (& r .kQueueEvents [0 ],int (file .Fd ()),unix .EVFILT_READ ,unix .EV_ADD ) 48unix .SetKevent (& r .kQueueEvents [1 ],int (r .cancelSignalReader .Fd ()),unix .EVFILT_READ ,unix .EV_ADD ) 49 50return r ,nil 51} 52 53// kqueueReader implements pollReader using the BSD kqueue API. 54type kqueueReader struct { 55reader io.Reader 56file File 57cancelSignalReader * os.File 58cancelSignalWriter * os.File 59kQueue int 60kQueueEvents [2 ]unix.Kevent_t 61mu sync.Mutex 62canceled bool 63} 64 65// Read reads data from the underlying reader. 66func (r * kqueueReader )Read (p []byte ) (int ,error ) { 67r .mu .Lock () 68if r .canceled { 69r .mu .Unlock () 70return 0 ,ErrCanceled 71 } 72r .mu .Unlock () 73 74return r .reader .Read (p ) 75} 76 77// Poll waits for data to be available to read with the given timeout. 78func (r * kqueueReader )Poll (timeout time.Duration ) (bool ,error ) { 79r .mu .Lock () 80if r .canceled { 81r .mu .Unlock () 82return false ,ErrCanceled 83 } 84r .mu .Unlock () 85 86events := make ([]unix.Kevent_t ,1 ) 87 88var ts * unix.Timespec 89if timeout >= 0 { 90t := unix .NsecToTimespec (timeout .Nanoseconds ()) 91ts = & t 92 } 93 94for { 95n ,err := unix .Kevent (r .kQueue ,r .kQueueEvents [:],events ,ts ) 96if errors .Is (err ,unix .EINTR ) { 97continue // try again if the syscall was interrupted 98 } 99 100if err != nil { 101return false ,fmt .Errorf ("kevent: %w" ,err ) 102 } 103 104if n == 0 { 105return false ,nil // timeout 106 } 107 108break 109 } 110 111ident := uint64 (events [0 ].Ident ) 112switch ident { 113case uint64 (r .file .Fd ()): 114return true ,nil 115case uint64 (r .cancelSignalReader .Fd ()): 116// remove signal from pipe 117var b [1 ]byte 118_ ,errRead := r .cancelSignalReader .Read (b [:]) 119if errRead != nil { 120return false ,fmt .Errorf ("reading cancel signal: %w" ,errRead ) 121 } 122return false ,ErrCanceled 123 } 124 125return false ,fmt .Errorf ("unknown error" ) 126} 127 128// Cancel cancels any ongoing poll or read operations. 129func (r * kqueueReader )Cancel ()bool { 130r .mu .Lock () 131r .canceled = true 132r .mu .Unlock () 133 134// send cancel signal 135_ ,err := r .cancelSignalWriter .Write ([]byte {'c' }) 136return err == nil 137} 138 139// Close closes the reader and releases any resources. 140func (r * kqueueReader )Close ()error { 141var errMsgs []string 142 143// close kqueue 144err := unix .Close (r .kQueue ) 145if err != nil { 146errMsgs = append (errMsgs ,fmt .Sprintf ("closing kqueue: %v" ,err )) 147 } 148 149// close pipe 150err = r .cancelSignalWriter .Close () 151if err != nil { 152errMsgs = append (errMsgs ,fmt .Sprintf ("closing cancel signal writer: %v" ,err )) 153 } 154 155err = r .cancelSignalReader .Close () 156if err != nil { 157errMsgs = append (errMsgs ,fmt .Sprintf ("closing cancel signal reader: %v" ,err )) 158 } 159 160if len (errMsgs )> 0 { 161return fmt .Errorf ("%s" ,strings .Join (errMsgs ,", " )) 162 } 163 164return nil 165}