github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/sysfs/file_windows.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"errors"
     5  	"syscall"
     6  	"unsafe"
     7  
     8  	"github.com/tetratelabs/wazero/experimental/sys"
     9  )
    10  
    11  const (
    12  	nonBlockingFileReadSupported  = true
    13  	nonBlockingFileWriteSupported = false
    14  
    15  	_ERROR_IO_INCOMPLETE = syscall.Errno(996)
    16  )
    17  
    18  var kernel32 = syscall.NewLazyDLL("kernel32.dll")
    19  
    20  // procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe
    21  var (
    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
    35  func 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  
    55  func writeFd(fd uintptr, buf []byte) (int, sys.Errno) {
    56  	return -1, sys.ENOSYS
    57  }
    58  
    59  func 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  
   110  func 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
   122  func 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  
   136  func rmdir(path string) sys.Errno {
   137  	err := syscall.Rmdir(path)
   138  	return sys.UnwrapOSError(err)
   139  }
   140  
   141  func 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  
   157  func 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  }