blob: 2a196cc76b42e75f3d7da1dd3ef5ebcba3f0b8d0 [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)
Victor Stinnereeeebcd2014-03-06 00:52:53 +010054 lsock.bind((host, 0))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070055 lsock.listen(1)
Victor Stinnereeeebcd2014-03-06 00:52:53 +010056 # On IPv6, ignore flow_info and scope_id
57 addr, port = lsock.getsockname()[:2]
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070058 csock = socket.socket(family, type, proto)
59 csock.setblocking(False)
60 try:
61 csock.connect((addr, port))
62 except (BlockingIOError, InterruptedError):
63 pass
64 except Exception:
65 lsock.close()
66 csock.close()
67 raise
68 ssock, _ = lsock.accept()
69 csock.setblocking(True)
70 lsock.close()
71 return (ssock, csock)
72
Guido van Rossuma8d630a2013-11-01 14:20:55 -070073
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070074# Replacement for os.pipe() using handles instead of fds
Guido van Rossuma8d630a2013-11-01 14:20:55 -070075
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070076
77def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
78 """Like os.pipe() but with overlapped support and using handles not fds."""
79 address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
80 (os.getpid(), next(_mmap_counter)))
81
82 if duplex:
83 openmode = _winapi.PIPE_ACCESS_DUPLEX
84 access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
85 obsize, ibsize = bufsize, bufsize
86 else:
87 openmode = _winapi.PIPE_ACCESS_INBOUND
88 access = _winapi.GENERIC_WRITE
89 obsize, ibsize = 0, bufsize
90
91 openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
92
93 if overlapped[0]:
94 openmode |= _winapi.FILE_FLAG_OVERLAPPED
95
96 if overlapped[1]:
97 flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
98 else:
99 flags_and_attribs = 0
100
101 h1 = h2 = None
102 try:
103 h1 = _winapi.CreateNamedPipe(
104 address, openmode, _winapi.PIPE_WAIT,
105 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
106
107 h2 = _winapi.CreateFile(
108 address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
109 flags_and_attribs, _winapi.NULL)
110
111 ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
112 ov.GetOverlappedResult(True)
113 return h1, h2
114 except:
115 if h1 is not None:
116 _winapi.CloseHandle(h1)
117 if h2 is not None:
118 _winapi.CloseHandle(h2)
119 raise
120
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700121
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700122# Wrapper for a pipe handle
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700123
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700124
125class PipeHandle:
126 """Wrapper for an overlapped pipe handle which is vaguely file-object like.
127
128 The IOCP event loop can use these instead of socket objects.
129 """
130 def __init__(self, handle):
131 self._handle = handle
132
133 @property
134 def handle(self):
135 return self._handle
136
137 def fileno(self):
138 return self._handle
139
140 def close(self, *, CloseHandle=_winapi.CloseHandle):
141 if self._handle != -1:
142 CloseHandle(self._handle)
143 self._handle = -1
144
145 __del__ = close
146
147 def __enter__(self):
148 return self
149
150 def __exit__(self, t, v, tb):
151 self.close()
152
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700153
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700154# Replacement for subprocess.Popen using overlapped pipe handles
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700155
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700156
157class Popen(subprocess.Popen):
158 """Replacement for subprocess.Popen using overlapped pipe handles.
159
160 The stdin, stdout, stderr are None or instances of PipeHandle.
161 """
162 def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
Guido van Rossum59691282013-10-30 14:52:03 -0700163 assert not kwds.get('universal_newlines')
164 assert kwds.get('bufsize', 0) == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700165 stdin_rfd = stdout_wfd = stderr_wfd = None
166 stdin_wh = stdout_rh = stderr_rh = None
167 if stdin == PIPE:
Guido van Rossum59691282013-10-30 14:52:03 -0700168 stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700169 stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
Guido van Rossum59691282013-10-30 14:52:03 -0700170 else:
171 stdin_rfd = stdin
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700172 if stdout == PIPE:
173 stdout_rh, stdout_wh = pipe(overlapped=(True, False))
174 stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700175 else:
176 stdout_wfd = stdout
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700177 if stderr == PIPE:
178 stderr_rh, stderr_wh = pipe(overlapped=(True, False))
179 stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700180 elif stderr == STDOUT:
181 stderr_wfd = stdout_wfd
182 else:
183 stderr_wfd = stderr
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700184 try:
Guido van Rossum59691282013-10-30 14:52:03 -0700185 super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700186 stderr=stderr_wfd, **kwds)
187 except:
188 for h in (stdin_wh, stdout_rh, stderr_rh):
Guido van Rossum59691282013-10-30 14:52:03 -0700189 if h is not None:
190 _winapi.CloseHandle(h)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700191 raise
192 else:
193 if stdin_wh is not None:
194 self.stdin = PipeHandle(stdin_wh)
195 if stdout_rh is not None:
196 self.stdout = PipeHandle(stdout_rh)
197 if stderr_rh is not None:
198 self.stderr = PipeHandle(stderr_rh)
199 finally:
200 if stdin == PIPE:
201 os.close(stdin_rfd)
202 if stdout == PIPE:
203 os.close(stdout_wfd)
204 if stderr == PIPE:
205 os.close(stderr_wfd)