file_windows.go

  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}