github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/select_windows.go (about) 1 package sysfs 2 3 import ( 4 "context" 5 "syscall" 6 "time" 7 "unsafe" 8 9 "github.com/tetratelabs/wazero/internal/platform" 10 ) 11 12 // wasiFdStdin is the constant value for stdin on Wasi. 13 // We need this constant because on Windows os.Stdin.Fd() != 0. 14 const wasiFdStdin = 0 15 16 // pollInterval is the interval between each calls to peekNamedPipe in pollNamedPipe 17 const pollInterval = 100 * time.Millisecond 18 19 var kernel32 = syscall.NewLazyDLL("kernel32.dll") 20 21 // procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe 22 var procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe") 23 24 // syscall_select emulates the select syscall on Windows for two, well-known cases, returns syscall.ENOSYS for all others. 25 // If r contains fd 0, and it is a regular file, then it immediately returns 1 (data ready on stdin) 26 // and r will have the fd 0 bit set. 27 // If r contains fd 0, and it is a FILE_TYPE_CHAR, then it invokes PeekNamedPipe to check the buffer for input; 28 // if there is data ready, then it returns 1 and r will have fd 0 bit set. 29 // If n==0 it will wait for the given timeout duration, but it will return syscall.ENOSYS if timeout is nil, 30 // i.e. it won't block indefinitely. 31 // 32 // Note: idea taken from https://stackoverflow.com/questions/6839508/test-if-stdin-has-input-for-c-windows-and-or-linux 33 // PeekNamedPipe: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe 34 // "GetFileType can assist in determining what device type the handle refers to. A console handle presents as FILE_TYPE_CHAR." 35 // https://learn.microsoft.com/en-us/windows/console/console-handles 36 func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { 37 if n == 0 { 38 // Don't block indefinitely. 39 if timeout == nil { 40 return -1, syscall.ENOSYS 41 } 42 time.Sleep(*timeout) 43 return 0, nil 44 } 45 if r.IsSet(wasiFdStdin) { 46 fileType, err := syscall.GetFileType(syscall.Stdin) 47 if err != nil { 48 return 0, err 49 } 50 if fileType&syscall.FILE_TYPE_CHAR != 0 { 51 res, err := pollNamedPipe(context.TODO(), syscall.Stdin, timeout) 52 if err != nil { 53 return -1, err 54 } 55 if !res { 56 r.Zero() 57 return 0, nil 58 } 59 } 60 r.Zero() 61 r.Set(wasiFdStdin) 62 return 1, nil 63 } 64 return -1, syscall.ENOSYS 65 } 66 67 // pollNamedPipe polls the given named pipe handle for the given duration. 68 // 69 // The implementation actually polls every 100 milliseconds until it reaches the given duration. 70 // The duration may be nil, in which case it will wait undefinely. The given ctx is 71 // used to allow for cancellation. Currently used only in tests. 72 func pollNamedPipe(ctx context.Context, pipeHandle syscall.Handle, duration *time.Duration) (bool, error) { 73 // Short circuit when the duration is zero. 74 if duration != nil && *duration == time.Duration(0) { 75 return peekNamedPipe(pipeHandle) 76 } 77 78 // Ticker that emits at every pollInterval. 79 tick := time.NewTicker(pollInterval) 80 tichCh := tick.C 81 defer tick.Stop() 82 83 // Timer that expires after the given duration. 84 // Initialize afterCh as nil: the select below will wait forever. 85 var afterCh <-chan time.Time 86 if duration != nil { 87 // If duration is not nil, instantiate the timer. 88 after := time.NewTimer(*duration) 89 defer after.Stop() 90 afterCh = after.C 91 } 92 93 for { 94 select { 95 case <-ctx.Done(): 96 return false, nil 97 case <-afterCh: 98 return false, nil 99 case <-tichCh: 100 res, err := peekNamedPipe(pipeHandle) 101 if err != nil { 102 return false, err 103 } 104 if res { 105 return true, nil 106 } 107 } 108 } 109 } 110 111 // peekNamedPipe partially exposes PeekNamedPipe from the Win32 API 112 // see https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe 113 func peekNamedPipe(handle syscall.Handle) (bool, error) { 114 var totalBytesAvail uint32 115 totalBytesPtr := unsafe.Pointer(&totalBytesAvail) 116 _, _, err := procPeekNamedPipe.Call( 117 uintptr(handle), // [in] HANDLE hNamedPipe, 118 0, // [out, optional] LPVOID lpBuffer, 119 0, // [in] DWORD nBufferSize, 120 0, // [out, optional] LPDWORD lpBytesRead 121 uintptr(totalBytesPtr), // [out, optional] LPDWORD lpTotalBytesAvail, 122 0) // [out, optional] LPDWORD lpBytesLeftThisMessage 123 if err == syscall.Errno(0) { 124 return totalBytesAvail > 0, nil 125 } 126 return totalBytesAvail > 0, err 127 }