You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
6.5 KiB

  1. """
  2. Various Windows specific bits and pieces
  3. """
  4. import sys
  5. if sys.platform != 'win32': # pragma: no cover
  6. raise ImportError('win32 only')
  7. import _winapi
  8. import itertools
  9. import msvcrt
  10. import os
  11. import socket
  12. import subprocess
  13. import tempfile
  14. __all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
  15. # Constants/globals
  16. BUFSIZE = 8192
  17. PIPE = subprocess.PIPE
  18. STDOUT = subprocess.STDOUT
  19. _mmap_counter = itertools.count()
  20. if hasattr(socket, 'socketpair'):
  21. # Since Python 3.5, socket.socketpair() is now also available on Windows
  22. socketpair = socket.socketpair
  23. else:
  24. # Replacement for socket.socketpair()
  25. def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
  26. """A socket pair usable as a self-pipe, for Windows.
  27. Origin: https://gist.github.com/4325783, by Geert Jansen.
  28. Public domain.
  29. """
  30. if family == socket.AF_INET:
  31. host = '127.0.0.1'
  32. elif family == socket.AF_INET6:
  33. host = '::1'
  34. else:
  35. raise ValueError("Only AF_INET and AF_INET6 socket address "
  36. "families are supported")
  37. if type != socket.SOCK_STREAM:
  38. raise ValueError("Only SOCK_STREAM socket type is supported")
  39. if proto != 0:
  40. raise ValueError("Only protocol zero is supported")
  41. # We create a connected TCP socket. Note the trick with setblocking(0)
  42. # that prevents us from having to create a thread.
  43. lsock = socket.socket(family, type, proto)
  44. try:
  45. lsock.bind((host, 0))
  46. lsock.listen(1)
  47. # On IPv6, ignore flow_info and scope_id
  48. addr, port = lsock.getsockname()[:2]
  49. csock = socket.socket(family, type, proto)
  50. try:
  51. csock.setblocking(False)
  52. try:
  53. csock.connect((addr, port))
  54. except (BlockingIOError, InterruptedError):
  55. pass
  56. csock.setblocking(True)
  57. ssock, _ = lsock.accept()
  58. except:
  59. csock.close()
  60. raise
  61. finally:
  62. lsock.close()
  63. return (ssock, csock)
  64. # Replacement for os.pipe() using handles instead of fds
  65. def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
  66. """Like os.pipe() but with overlapped support and using handles not fds."""
  67. address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
  68. (os.getpid(), next(_mmap_counter)))
  69. if duplex:
  70. openmode = _winapi.PIPE_ACCESS_DUPLEX
  71. access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
  72. obsize, ibsize = bufsize, bufsize
  73. else:
  74. openmode = _winapi.PIPE_ACCESS_INBOUND
  75. access = _winapi.GENERIC_WRITE
  76. obsize, ibsize = 0, bufsize
  77. openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
  78. if overlapped[0]:
  79. openmode |= _winapi.FILE_FLAG_OVERLAPPED
  80. if overlapped[1]:
  81. flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
  82. else:
  83. flags_and_attribs = 0
  84. h1 = h2 = None
  85. try:
  86. h1 = _winapi.CreateNamedPipe(
  87. address, openmode, _winapi.PIPE_WAIT,
  88. 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
  89. h2 = _winapi.CreateFile(
  90. address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
  91. flags_and_attribs, _winapi.NULL)
  92. ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
  93. ov.GetOverlappedResult(True)
  94. return h1, h2
  95. except:
  96. if h1 is not None:
  97. _winapi.CloseHandle(h1)
  98. if h2 is not None:
  99. _winapi.CloseHandle(h2)
  100. raise
  101. # Wrapper for a pipe handle
  102. class PipeHandle:
  103. """Wrapper for an overlapped pipe handle which is vaguely file-object like.
  104. The IOCP event loop can use these instead of socket objects.
  105. """
  106. def __init__(self, handle):
  107. self._handle = handle
  108. def __repr__(self):
  109. if self._handle is not None:
  110. handle = 'handle=%r' % self._handle
  111. else:
  112. handle = 'closed'
  113. return '<%s %s>' % (self.__class__.__name__, handle)
  114. @property
  115. def handle(self):
  116. return self._handle
  117. def fileno(self):
  118. if self._handle is None:
  119. raise ValueError("I/O operatioon on closed pipe")
  120. return self._handle
  121. def close(self, *, CloseHandle=_winapi.CloseHandle):
  122. if self._handle is not None:
  123. CloseHandle(self._handle)
  124. self._handle = None
  125. __del__ = close
  126. def __enter__(self):
  127. return self
  128. def __exit__(self, t, v, tb):
  129. self.close()
  130. # Replacement for subprocess.Popen using overlapped pipe handles
  131. class Popen(subprocess.Popen):
  132. """Replacement for subprocess.Popen using overlapped pipe handles.
  133. The stdin, stdout, stderr are None or instances of PipeHandle.
  134. """
  135. def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
  136. assert not kwds.get('universal_newlines')
  137. assert kwds.get('bufsize', 0) == 0
  138. stdin_rfd = stdout_wfd = stderr_wfd = None
  139. stdin_wh = stdout_rh = stderr_rh = None
  140. if stdin == PIPE:
  141. stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
  142. stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
  143. else:
  144. stdin_rfd = stdin
  145. if stdout == PIPE:
  146. stdout_rh, stdout_wh = pipe(overlapped=(True, False))
  147. stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
  148. else:
  149. stdout_wfd = stdout
  150. if stderr == PIPE:
  151. stderr_rh, stderr_wh = pipe(overlapped=(True, False))
  152. stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
  153. elif stderr == STDOUT:
  154. stderr_wfd = stdout_wfd
  155. else:
  156. stderr_wfd = stderr
  157. try:
  158. super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
  159. stderr=stderr_wfd, **kwds)
  160. except:
  161. for h in (stdin_wh, stdout_rh, stderr_rh):
  162. if h is not None:
  163. _winapi.CloseHandle(h)
  164. raise
  165. else:
  166. if stdin_wh is not None:
  167. self.stdin = PipeHandle(stdin_wh)
  168. if stdout_rh is not None:
  169. self.stdout = PipeHandle(stdout_rh)
  170. if stderr_rh is not None:
  171. self.stderr = PipeHandle(stderr_rh)
  172. finally:
  173. if stdin == PIPE:
  174. os.close(stdin_rfd)
  175. if stdout == PIPE:
  176. os.close(stdout_wfd)
  177. if stderr == PIPE:
  178. os.close(stderr_wfd)