blob: c6e4bc9e46bce09e89893403b07e3681858961a0 [file] [log] [blame]
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07001"""
2Various Windows specific bits and pieces
3"""
4
5import sys
6
7if sys.platform != 'win32': # pragma: no cover
8 raise ImportError('win32 only')
9
10import socket
11import itertools
12import msvcrt
13import os
14import subprocess
15import tempfile
16import _winapi
17
18
19__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
20
Guido van Rossuma8d630a2013-11-01 14:20:55 -070021
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070022# Constants/globals
Guido van Rossuma8d630a2013-11-01 14:20:55 -070023
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070024
25BUFSIZE = 8192
26PIPE = subprocess.PIPE
Guido van Rossum59691282013-10-30 14:52:03 -070027STDOUT = subprocess.STDOUT
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070028_mmap_counter = itertools.count()
29
Guido van Rossuma8d630a2013-11-01 14:20:55 -070030
Victor Stinnerf67f4602014-10-14 22:56:25 +020031if hasattr(socket, 'socketpair'):
32 # Since Python 3.5, socket.socketpair() is now also available on Windows
33 socketpair = socket.socketpair
34else:
35 # Replacement for socket.socketpair()
36 def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
37 """A socket pair usable as a self-pipe, for Windows.
Guido van Rossuma8d630a2013-11-01 14:20:55 -070038
Victor Stinnerf67f4602014-10-14 22:56:25 +020039 Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain.
40 """
41 if family == socket.AF_INET:
42 host = '127.0.0.1'
43 elif family == socket.AF_INET6:
44 host = '::1'
45 else:
46 raise ValueError("Only AF_INET and AF_INET6 socket address families "
47 "are supported")
48 if type != socket.SOCK_STREAM:
49 raise ValueError("Only SOCK_STREAM socket type is supported")
50 if proto != 0:
51 raise ValueError("Only protocol zero is supported")
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070052
Victor Stinnerf67f4602014-10-14 22:56:25 +020053 # We create a connected TCP socket. Note the trick with setblocking(0)
54 # that prevents us from having to create a thread.
55 lsock = socket.socket(family, type, proto)
Victor Stinnera9fa2662014-06-04 00:12:28 +020056 try:
Victor Stinnerf67f4602014-10-14 22:56:25 +020057 lsock.bind((host, 0))
58 lsock.listen(1)
59 # On IPv6, ignore flow_info and scope_id
60 addr, port = lsock.getsockname()[:2]
61 csock = socket.socket(family, type, proto)
Victor Stinnera9fa2662014-06-04 00:12:28 +020062 try:
Victor Stinnerf67f4602014-10-14 22:56:25 +020063 csock.setblocking(False)
64 try:
65 csock.connect((addr, port))
66 except (BlockingIOError, InterruptedError):
67 pass
68 csock.setblocking(True)
69 ssock, _ = lsock.accept()
70 except:
71 csock.close()
72 raise
73 finally:
74 lsock.close()
75 return (ssock, csock)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070076
Guido van Rossuma8d630a2013-11-01 14:20:55 -070077
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070078# Replacement for os.pipe() using handles instead of fds
Guido van Rossuma8d630a2013-11-01 14:20:55 -070079
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070080
81def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
82 """Like os.pipe() but with overlapped support and using handles not fds."""
83 address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
84 (os.getpid(), next(_mmap_counter)))
85
86 if duplex:
87 openmode = _winapi.PIPE_ACCESS_DUPLEX
88 access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
89 obsize, ibsize = bufsize, bufsize
90 else:
91 openmode = _winapi.PIPE_ACCESS_INBOUND
92 access = _winapi.GENERIC_WRITE
93 obsize, ibsize = 0, bufsize
94
95 openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
96
97 if overlapped[0]:
98 openmode |= _winapi.FILE_FLAG_OVERLAPPED
99
100 if overlapped[1]:
101 flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
102 else:
103 flags_and_attribs = 0
104
105 h1 = h2 = None
106 try:
107 h1 = _winapi.CreateNamedPipe(
108 address, openmode, _winapi.PIPE_WAIT,
109 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
110
111 h2 = _winapi.CreateFile(
112 address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
113 flags_and_attribs, _winapi.NULL)
114
115 ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
116 ov.GetOverlappedResult(True)
117 return h1, h2
118 except:
119 if h1 is not None:
120 _winapi.CloseHandle(h1)
121 if h2 is not None:
122 _winapi.CloseHandle(h2)
123 raise
124
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700125
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700126# Wrapper for a pipe handle
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700127
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700128
129class PipeHandle:
130 """Wrapper for an overlapped pipe handle which is vaguely file-object like.
131
132 The IOCP event loop can use these instead of socket objects.
133 """
134 def __init__(self, handle):
135 self._handle = handle
136
Victor Stinner1b9763d2014-12-18 23:47:27 +0100137 def __repr__(self):
138 if self._handle != -1:
139 handle = 'handle=%r' % self._handle
140 else:
141 handle = 'closed'
142 return '<%s %s>' % (self.__class__.__name__, handle)
143
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700144 @property
145 def handle(self):
146 return self._handle
147
148 def fileno(self):
149 return self._handle
150
151 def close(self, *, CloseHandle=_winapi.CloseHandle):
152 if self._handle != -1:
153 CloseHandle(self._handle)
154 self._handle = -1
155
156 __del__ = close
157
158 def __enter__(self):
159 return self
160
161 def __exit__(self, t, v, tb):
162 self.close()
163
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700164
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700165# Replacement for subprocess.Popen using overlapped pipe handles
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700166
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700167
168class Popen(subprocess.Popen):
169 """Replacement for subprocess.Popen using overlapped pipe handles.
170
171 The stdin, stdout, stderr are None or instances of PipeHandle.
172 """
173 def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
Guido van Rossum59691282013-10-30 14:52:03 -0700174 assert not kwds.get('universal_newlines')
175 assert kwds.get('bufsize', 0) == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700176 stdin_rfd = stdout_wfd = stderr_wfd = None
177 stdin_wh = stdout_rh = stderr_rh = None
178 if stdin == PIPE:
Guido van Rossum59691282013-10-30 14:52:03 -0700179 stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700180 stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
Guido van Rossum59691282013-10-30 14:52:03 -0700181 else:
182 stdin_rfd = stdin
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700183 if stdout == PIPE:
184 stdout_rh, stdout_wh = pipe(overlapped=(True, False))
185 stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700186 else:
187 stdout_wfd = stdout
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700188 if stderr == PIPE:
189 stderr_rh, stderr_wh = pipe(overlapped=(True, False))
190 stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700191 elif stderr == STDOUT:
192 stderr_wfd = stdout_wfd
193 else:
194 stderr_wfd = stderr
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700195 try:
Guido van Rossum59691282013-10-30 14:52:03 -0700196 super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700197 stderr=stderr_wfd, **kwds)
198 except:
199 for h in (stdin_wh, stdout_rh, stderr_rh):
Guido van Rossum59691282013-10-30 14:52:03 -0700200 if h is not None:
201 _winapi.CloseHandle(h)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700202 raise
203 else:
204 if stdin_wh is not None:
205 self.stdin = PipeHandle(stdin_wh)
206 if stdout_rh is not None:
207 self.stdout = PipeHandle(stdout_rh)
208 if stderr_rh is not None:
209 self.stderr = PipeHandle(stderr_rh)
210 finally:
211 if stdin == PIPE:
212 os.close(stdin_rfd)
213 if stdout == PIPE:
214 os.close(stdout_wfd)
215 if stderr == PIPE:
216 os.close(stderr_wfd)