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