1package sysfs
2
3import (
4 "errors"
5 "syscall"
6 "unsafe"
7
8 "github.com/tetratelabs/wazero/experimental/sys"
9)
10
11const (
12 nonBlockingFileReadSupported = true
13 nonBlockingFileWriteSupported = false
14
15 _ERROR_IO_INCOMPLETE = syscall.Errno(996)
16)
17
18var kernel32 = syscall.NewLazyDLL("kernel32.dll")
19
20// procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe
21var (
22 // procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe
23 procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe")
24 // procGetOverlappedResult is the syscall.LazyProc in kernel32 for GetOverlappedResult
25 procGetOverlappedResult = kernel32.NewProc("GetOverlappedResult")
26 // procCreateEventW is the syscall.LazyProc in kernel32 for CreateEventW
27 procCreateEventW = kernel32.NewProc("CreateEventW")
28)
29
30// readFd returns ENOSYS on unsupported platforms.
31//
32// PeekNamedPipe: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
33// "GetFileType can assist in determining what device type the handle refers to. A console handle presents as FILE_TYPE_CHAR."
34// https://learn.microsoft.com/en-us/windows/console/console-handles
35func readFd(fd uintptr, buf []byte) (int, sys.Errno) {
36 handle := syscall.Handle(fd)
37 fileType, err := syscall.GetFileType(handle)
38 if err != nil {
39 return 0, sys.UnwrapOSError(err)
40 }
41 if fileType&syscall.FILE_TYPE_CHAR == 0 {
42 return -1, sys.ENOSYS
43 }
44 n, errno := peekNamedPipe(handle)
45 if errno == syscall.ERROR_BROKEN_PIPE {
46 return 0, 0
47 }
48 if n == 0 {
49 return -1, sys.EAGAIN
50 }
51 un, err := syscall.Read(handle, buf[0:n])
52 return un, sys.UnwrapOSError(err)
53}
54
55func writeFd(fd uintptr, buf []byte) (int, sys.Errno) {
56 return -1, sys.ENOSYS
57}
58
59func readSocket(h uintptr, buf []byte) (int, sys.Errno) {
60 // Poll the socket to ensure that we never perform a blocking/overlapped Read.
61 //
62 // When the socket is closed by the remote peer, wsaPoll will return n=1 and
63 // errno=0, and syscall.ReadFile will return n=0 and errno=0 -- which indicates
64 // io.EOF.
65 if n, errno := wsaPoll(
66 []pollFd{newPollFd(h, _POLLIN, 0)}, 0); !errors.Is(errno, sys.Errno(0)) {
67 return 0, sys.UnwrapOSError(errno)
68 } else if n <= 0 {
69 return 0, sys.EAGAIN
70 }
71
72 // Properly use overlapped result.
73 //
74 // If hFile was opened with FILE_FLAG_OVERLAPPED, the following conditions are in effect:
75 // - The lpOverlapped parameter must point to a valid and unique OVERLAPPED structure,
76 // otherwise the function can incorrectly report that the read operation is complete.
77 // - The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult
78 // function to get the actual number of bytes read. If the hFile parameter is associated
79 // with an I/O completion port, you can also get the number of bytes read by calling the
80 // GetQueuedCompletionStatus function.
81 //
82 // We are currently skipping checking if hFile was opened with FILE_FLAG_OVERLAPPED but using
83 // both lpOverlapped and lpNumberOfBytesRead.
84 var overlapped syscall.Overlapped
85
86 // Create an event to wait on.
87 if hEvent, err := createEventW(nil, true, false, nil); err != 0 {
88 return 0, sys.UnwrapOSError(err)
89 } else {
90 overlapped.HEvent = syscall.Handle(hEvent)
91 }
92
93 var done uint32
94 errno := syscall.ReadFile(syscall.Handle(h), buf, &done, &overlapped)
95 if errors.Is(errno, syscall.ERROR_IO_PENDING) {
96 errno = syscall.CancelIo(syscall.Handle(h))
97 if errno != nil {
98 return 0, sys.UnwrapOSError(errno) // This is a fatal error. CancelIo failed.
99 }
100
101 done, errno = getOverlappedResult(syscall.Handle(h), &overlapped, true) // wait for I/O to complete(cancel or finish). Overwrite done and errno.
102 if errors.Is(errno, syscall.ERROR_OPERATION_ABORTED) {
103 return int(done), sys.EAGAIN // This is one of the expected behavior, I/O was cancelled(completed) before finished.
104 }
105 }
106
107 return int(done), sys.UnwrapOSError(errno)
108}
109
110func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) {
111 var done uint32
112 var overlapped syscall.Overlapped
113 errno := syscall.WriteFile(syscall.Handle(fd), buf, &done, &overlapped)
114 if errors.Is(errno, syscall.ERROR_IO_PENDING) {
115 errno = syscall.EAGAIN
116 }
117 return int(done), sys.UnwrapOSError(errno)
118}
119
120// peekNamedPipe partially exposes PeekNamedPipe from the Win32 API
121// see https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
122func peekNamedPipe(handle syscall.Handle) (uint32, syscall.Errno) {
123 var totalBytesAvail uint32
124 totalBytesPtr := unsafe.Pointer(&totalBytesAvail)
125 _, _, errno := syscall.SyscallN(
126 procPeekNamedPipe.Addr(),
127 uintptr(handle), // [in] HANDLE hNamedPipe,
128 0, // [out, optional] LPVOID lpBuffer,
129 0, // [in] DWORD nBufferSize,
130 0, // [out, optional] LPDWORD lpBytesRead
131 uintptr(totalBytesPtr), // [out, optional] LPDWORD lpTotalBytesAvail,
132 0) // [out, optional] LPDWORD lpBytesLeftThisMessage
133 return totalBytesAvail, errno
134}
135
136func rmdir(path string) sys.Errno {
137 err := syscall.Rmdir(path)
138 return sys.UnwrapOSError(err)
139}
140
141func getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, wait bool) (uint32, syscall.Errno) {
142 var totalBytesAvail uint32
143 var bwait uintptr
144 if wait {
145 bwait = 0xFFFFFFFF
146 }
147 totalBytesPtr := unsafe.Pointer(&totalBytesAvail)
148 _, _, errno := syscall.SyscallN(
149 procGetOverlappedResult.Addr(),
150 uintptr(handle), // [in] HANDLE hFile,
151 uintptr(unsafe.Pointer(overlapped)), // [in] LPOVERLAPPED lpOverlapped,
152 uintptr(totalBytesPtr), // [out] LPDWORD lpNumberOfBytesTransferred,
153 bwait) // [in] BOOL bWait
154 return totalBytesAvail, errno
155}
156
157func createEventW(lpEventAttributes *syscall.SecurityAttributes, bManualReset bool, bInitialState bool, lpName *uint16) (uintptr, syscall.Errno) {
158 var manualReset uintptr
159 var initialState uintptr
160 if bManualReset {
161 manualReset = 1
162 }
163 if bInitialState {
164 initialState = 1
165 }
166 handle, _, errno := syscall.SyscallN(
167 procCreateEventW.Addr(),
168 uintptr(unsafe.Pointer(lpEventAttributes)), // [in] LPSECURITY_ATTRIBUTES lpEventAttributes,
169 manualReset, // [in] BOOL bManualReset,
170 initialState, // [in] BOOL bInitialState,
171 uintptr(unsafe.Pointer(lpName)), // [in, opt]LPCWSTR lpName,
172 )
173
174 return handle, errno
175}