blob: f7f2f3580adee836b106ec9e706bd48c41d200c7 [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
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070031# Replacement for socket.socketpair()
Guido van Rossuma8d630a2013-11-01 14:20:55 -070032
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070033
34def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
35 """A socket pair usable as a self-pipe, for Windows.
36
37 Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain.
38 """
Victor Stinnereeeebcd2014-03-06 00:52:53 +010039 if family == socket.AF_INET:
40 host = '127.0.0.1'
41 elif family == socket.AF_INET6:
42 host = '::1'
43 else:
44 raise ValueError("Ony AF_INET and AF_INET6 socket address families "
45 "are supported")
46 if type != socket.SOCK_STREAM:
47 raise ValueError("Only SOCK_STREAM socket type is supported")
48 if proto != 0:
49 raise ValueError("Only protocol zero is supported")
50
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070051 # We create a connected TCP socket. Note the trick with setblocking(0)
52 # that prevents us from having to create a thread.
53 lsock = socket.socket(family, type, proto)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070054 try:
Victor Stinnera9fa2662014-06-04 00:12:28 +020055 lsock.bind((host, 0))
56 lsock.listen(1)
57 # On IPv6, ignore flow_info and scope_id
58 addr, port = lsock.getsockname()[:2]
59 csock = socket.socket(family, type, proto)
60 try:
61 csock.setblocking(False)
62 try:
63 csock.connect((addr, port))
64 except (BlockingIOError, InterruptedError):
65 pass
66 ssock, _ = lsock.accept()
67 csock.setblocking(True)
68 except:
69 csock.close()
70 raise
71 finally:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070072 lsock.close()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070073 return (ssock, csock)
74
Guido van Rossuma8d630a2013-11-01 14:20:55 -070075
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070076# Replacement for os.pipe() using handles instead of fds
Guido van Rossuma8d630a2013-11-01 14:20:55 -070077
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070078
79def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
80 """Like os.pipe() but with overlapped support and using handles not fds."""
81 address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
82 (os.getpid(), next(_mmap_counter)))
83
84 if duplex:
85 openmode = _winapi.PIPE_ACCESS_DUPLEX
86 access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
87 obsize, ibsize = bufsize, bufsize
88 else:
89 openmode = _winapi.PIPE_ACCESS_INBOUND
90 access = _winapi.GENERIC_WRITE
91 obsize, ibsize = 0, bufsize
92
93 openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
94
95 if overlapped[0]:
96 openmode |= _winapi.FILE_FLAG_OVERLAPPED
97
98 if overlapped[1]:
99 flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
100 else:
101 flags_and_attribs = 0
102
103 h1 = h2 = None
104 try:
105 h1 = _winapi.CreateNamedPipe(
106 address, openmode, _winapi.PIPE_WAIT,
107 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
108
109 h2 = _winapi.CreateFile(
110 address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
111 flags_and_attribs, _winapi.NULL)
112
113 ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
114 ov.GetOverlappedResult(True)
115 return h1, h2
116 except:
117 if h1 is not None:
118 _winapi.CloseHandle(h1)
119 if h2 is not None:
120 _winapi.CloseHandle(h2)
121 raise
122
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700123
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700124# Wrapper for a pipe handle
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700125
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700126
127class PipeHandle:
128 """Wrapper for an overlapped pipe handle which is vaguely file-object like.
129
130 The IOCP event loop can use these instead of socket objects.
131 """
132 def __init__(self, handle):
133 self._handle = handle
134
135 @property
136 def handle(self):
137 return self._handle
138
139 def fileno(self):
140 return self._handle
141
142 def close(self, *, CloseHandle=_winapi.CloseHandle):
143 if self._handle != -1:
144 CloseHandle(self._handle)
145 self._handle = -1
146
147 __del__ = close
148
149 def __enter__(self):
150 return self
151
152 def __exit__(self, t, v, tb):
153 self.close()
154
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700155
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700156# Replacement for subprocess.Popen using overlapped pipe handles
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700157
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700158
159class Popen(subprocess.Popen):
160 """Replacement for subprocess.Popen using overlapped pipe handles.
161
162 The stdin, stdout, stderr are None or instances of PipeHandle.
163 """
164 def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
Guido van Rossum59691282013-10-30 14:52:03 -0700165 assert not kwds.get('universal_newlines')
166 assert kwds.get('bufsize', 0) == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700167 stdin_rfd = stdout_wfd = stderr_wfd = None
168 stdin_wh = stdout_rh = stderr_rh = None
169 if stdin == PIPE:
Guido van Rossum59691282013-10-30 14:52:03 -0700170 stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700171 stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
Guido van Rossum59691282013-10-30 14:52:03 -0700172 else:
173 stdin_rfd = stdin
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700174 if stdout == PIPE:
175 stdout_rh, stdout_wh = pipe(overlapped=(True, False))
176 stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700177 else:
178 stdout_wfd = stdout
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700179 if stderr == PIPE:
180 stderr_rh, stderr_wh = pipe(overlapped=(True, False))
181 stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700182 elif stderr == STDOUT:
183 stderr_wfd = stdout_wfd
184 else:
185 stderr_wfd = stderr
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700186 try:
Guido van Rossum59691282013-10-30 14:52:03 -0700187 super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700188 stderr=stderr_wfd, **kwds)
189 except:
190 for h in (stdin_wh, stdout_rh, stderr_rh):
Guido van Rossum59691282013-10-30 14:52:03 -0700191 if h is not None:
192 _winapi.CloseHandle(h)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700193 raise
194 else:
195 if stdin_wh is not None:
196 self.stdin = PipeHandle(stdin_wh)
197 if stdout_rh is not None:
198 self.stdout = PipeHandle(stdout_rh)
199 if stderr_rh is not None:
200 self.stderr = PipeHandle(stderr_rh)
201 finally:
202 if stdin == PIPE:
203 os.close(stdin_rfd)
204 if stdout == PIPE:
205 os.close(stdout_wfd)
206 if stderr == PIPE:
207 os.close(stderr_wfd)