blob: aa1c0648067352b64e9623e207dbc5054c069bae [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 """
39 # We create a connected TCP socket. Note the trick with setblocking(0)
40 # that prevents us from having to create a thread.
41 lsock = socket.socket(family, type, proto)
42 lsock.bind(('localhost', 0))
43 lsock.listen(1)
44 addr, port = lsock.getsockname()
45 csock = socket.socket(family, type, proto)
46 csock.setblocking(False)
47 try:
48 csock.connect((addr, port))
49 except (BlockingIOError, InterruptedError):
50 pass
51 except Exception:
52 lsock.close()
53 csock.close()
54 raise
55 ssock, _ = lsock.accept()
56 csock.setblocking(True)
57 lsock.close()
58 return (ssock, csock)
59
Guido van Rossuma8d630a2013-11-01 14:20:55 -070060
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070061# Replacement for os.pipe() using handles instead of fds
Guido van Rossuma8d630a2013-11-01 14:20:55 -070062
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070063
64def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
65 """Like os.pipe() but with overlapped support and using handles not fds."""
66 address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
67 (os.getpid(), next(_mmap_counter)))
68
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
78 openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
79
80 if overlapped[0]:
81 openmode |= _winapi.FILE_FLAG_OVERLAPPED
82
83 if overlapped[1]:
84 flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
85 else:
86 flags_and_attribs = 0
87
88 h1 = h2 = None
89 try:
90 h1 = _winapi.CreateNamedPipe(
91 address, openmode, _winapi.PIPE_WAIT,
92 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
93
94 h2 = _winapi.CreateFile(
95 address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
96 flags_and_attribs, _winapi.NULL)
97
98 ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
99 ov.GetOverlappedResult(True)
100 return h1, h2
101 except:
102 if h1 is not None:
103 _winapi.CloseHandle(h1)
104 if h2 is not None:
105 _winapi.CloseHandle(h2)
106 raise
107
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700108
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700109# Wrapper for a pipe handle
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700110
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700111
112class PipeHandle:
113 """Wrapper for an overlapped pipe handle which is vaguely file-object like.
114
115 The IOCP event loop can use these instead of socket objects.
116 """
117 def __init__(self, handle):
118 self._handle = handle
119
120 @property
121 def handle(self):
122 return self._handle
123
124 def fileno(self):
125 return self._handle
126
127 def close(self, *, CloseHandle=_winapi.CloseHandle):
128 if self._handle != -1:
129 CloseHandle(self._handle)
130 self._handle = -1
131
132 __del__ = close
133
134 def __enter__(self):
135 return self
136
137 def __exit__(self, t, v, tb):
138 self.close()
139
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700140
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700141# Replacement for subprocess.Popen using overlapped pipe handles
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700142
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700143
144class Popen(subprocess.Popen):
145 """Replacement for subprocess.Popen using overlapped pipe handles.
146
147 The stdin, stdout, stderr are None or instances of PipeHandle.
148 """
149 def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
Guido van Rossum59691282013-10-30 14:52:03 -0700150 assert not kwds.get('universal_newlines')
151 assert kwds.get('bufsize', 0) == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700152 stdin_rfd = stdout_wfd = stderr_wfd = None
153 stdin_wh = stdout_rh = stderr_rh = None
154 if stdin == PIPE:
Guido van Rossum59691282013-10-30 14:52:03 -0700155 stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700156 stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
Guido van Rossum59691282013-10-30 14:52:03 -0700157 else:
158 stdin_rfd = stdin
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700159 if stdout == PIPE:
160 stdout_rh, stdout_wh = pipe(overlapped=(True, False))
161 stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700162 else:
163 stdout_wfd = stdout
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700164 if stderr == PIPE:
165 stderr_rh, stderr_wh = pipe(overlapped=(True, False))
166 stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700167 elif stderr == STDOUT:
168 stderr_wfd = stdout_wfd
169 else:
170 stderr_wfd = stderr
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700171 try:
Guido van Rossum59691282013-10-30 14:52:03 -0700172 super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700173 stderr=stderr_wfd, **kwds)
174 except:
175 for h in (stdin_wh, stdout_rh, stderr_rh):
Guido van Rossum59691282013-10-30 14:52:03 -0700176 if h is not None:
177 _winapi.CloseHandle(h)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700178 raise
179 else:
180 if stdin_wh is not None:
181 self.stdin = PipeHandle(stdin_wh)
182 if stdout_rh is not None:
183 self.stdout = PipeHandle(stdout_rh)
184 if stderr_rh is not None:
185 self.stderr = PipeHandle(stderr_rh)
186 finally:
187 if stdin == PIPE:
188 os.close(stdin_rfd)
189 if stdout == PIPE:
190 os.close(stdout_wfd)
191 if stderr == PIPE:
192 os.close(stderr_wfd)