blob: 9e22f6e0740e0c1e92bbf3a19406763d75296212 [file] [log] [blame]
Yury Selivanov6370f342017-12-10 18:36:12 -05001"""Various Windows specific bits and pieces."""
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07002
3import sys
4
5if sys.platform != 'win32': # pragma: no cover
6 raise ImportError('win32 only')
7
Victor Stinner29ad0112015-01-15 00:04:21 +01008import _winapi
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07009import itertools
10import msvcrt
11import os
12import subprocess
13import tempfile
Victor Stinner978a9af2015-01-29 17:50:58 +010014import warnings
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070015
16
Yury Selivanov6370f342017-12-10 18:36:12 -050017__all__ = 'pipe', 'Popen', 'PIPE', 'PipeHandle'
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070018
Guido van Rossuma8d630a2013-11-01 14:20:55 -070019
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070020# Constants/globals
Guido van Rossuma8d630a2013-11-01 14:20:55 -070021
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070022
23BUFSIZE = 8192
24PIPE = subprocess.PIPE
Guido van Rossum59691282013-10-30 14:52:03 -070025STDOUT = subprocess.STDOUT
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070026_mmap_counter = itertools.count()
27
Guido van Rossuma8d630a2013-11-01 14:20:55 -070028
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070029# Replacement for os.pipe() using handles instead of fds
Guido van Rossuma8d630a2013-11-01 14:20:55 -070030
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070031
32def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
33 """Like os.pipe() but with overlapped support and using handles not fds."""
Yury Selivanov6370f342017-12-10 18:36:12 -050034 address = tempfile.mktemp(
35 prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
36 os.getpid(), next(_mmap_counter)))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070037
38 if duplex:
39 openmode = _winapi.PIPE_ACCESS_DUPLEX
40 access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
41 obsize, ibsize = bufsize, bufsize
42 else:
43 openmode = _winapi.PIPE_ACCESS_INBOUND
44 access = _winapi.GENERIC_WRITE
45 obsize, ibsize = 0, bufsize
46
47 openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
48
49 if overlapped[0]:
50 openmode |= _winapi.FILE_FLAG_OVERLAPPED
51
52 if overlapped[1]:
53 flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
54 else:
55 flags_and_attribs = 0
56
57 h1 = h2 = None
58 try:
59 h1 = _winapi.CreateNamedPipe(
60 address, openmode, _winapi.PIPE_WAIT,
61 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
62
63 h2 = _winapi.CreateFile(
64 address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
65 flags_and_attribs, _winapi.NULL)
66
67 ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
68 ov.GetOverlappedResult(True)
69 return h1, h2
70 except:
71 if h1 is not None:
72 _winapi.CloseHandle(h1)
73 if h2 is not None:
74 _winapi.CloseHandle(h2)
75 raise
76
Guido van Rossuma8d630a2013-11-01 14:20:55 -070077
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070078# Wrapper for a pipe handle
Guido van Rossuma8d630a2013-11-01 14:20:55 -070079
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070080
81class PipeHandle:
82 """Wrapper for an overlapped pipe handle which is vaguely file-object like.
83
84 The IOCP event loop can use these instead of socket objects.
85 """
86 def __init__(self, handle):
87 self._handle = handle
88
Victor Stinner1b9763d2014-12-18 23:47:27 +010089 def __repr__(self):
Victor Stinner29ad0112015-01-15 00:04:21 +010090 if self._handle is not None:
Yury Selivanov6370f342017-12-10 18:36:12 -050091 handle = f'handle={self._handle!r}'
Victor Stinner1b9763d2014-12-18 23:47:27 +010092 else:
93 handle = 'closed'
Yury Selivanov6370f342017-12-10 18:36:12 -050094 return f'<{self.__class__.__name__} {handle}>'
Victor Stinner1b9763d2014-12-18 23:47:27 +010095
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070096 @property
97 def handle(self):
98 return self._handle
99
100 def fileno(self):
Victor Stinner2a3f38f2015-01-26 15:03:44 +0100101 if self._handle is None:
Jim Fasarakis-Hilliard1e73dbb2017-03-26 23:59:08 +0300102 raise ValueError("I/O operation on closed pipe")
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700103 return self._handle
104
105 def close(self, *, CloseHandle=_winapi.CloseHandle):
Victor Stinner29ad0112015-01-15 00:04:21 +0100106 if self._handle is not None:
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700107 CloseHandle(self._handle)
Victor Stinner29ad0112015-01-15 00:04:21 +0100108 self._handle = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700109
Victor Stinner978a9af2015-01-29 17:50:58 +0100110 def __del__(self):
111 if self._handle is not None:
Yury Selivanov6370f342017-12-10 18:36:12 -0500112 warnings.warn(f"unclosed {self!r}", ResourceWarning,
Victor Stinnere19558a2016-03-23 00:28:08 +0100113 source=self)
Victor Stinner978a9af2015-01-29 17:50:58 +0100114 self.close()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700115
116 def __enter__(self):
117 return self
118
119 def __exit__(self, t, v, tb):
120 self.close()
121
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700122
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700123# Replacement for subprocess.Popen using overlapped pipe handles
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700124
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700125
126class Popen(subprocess.Popen):
127 """Replacement for subprocess.Popen using overlapped pipe handles.
128
129 The stdin, stdout, stderr are None or instances of PipeHandle.
130 """
131 def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
Guido van Rossum59691282013-10-30 14:52:03 -0700132 assert not kwds.get('universal_newlines')
133 assert kwds.get('bufsize', 0) == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700134 stdin_rfd = stdout_wfd = stderr_wfd = None
135 stdin_wh = stdout_rh = stderr_rh = None
136 if stdin == PIPE:
Guido van Rossum59691282013-10-30 14:52:03 -0700137 stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700138 stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
Guido van Rossum59691282013-10-30 14:52:03 -0700139 else:
140 stdin_rfd = stdin
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700141 if stdout == PIPE:
142 stdout_rh, stdout_wh = pipe(overlapped=(True, False))
143 stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700144 else:
145 stdout_wfd = stdout
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700146 if stderr == PIPE:
147 stderr_rh, stderr_wh = pipe(overlapped=(True, False))
148 stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700149 elif stderr == STDOUT:
150 stderr_wfd = stdout_wfd
151 else:
152 stderr_wfd = stderr
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700153 try:
Guido van Rossum59691282013-10-30 14:52:03 -0700154 super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700155 stderr=stderr_wfd, **kwds)
156 except:
157 for h in (stdin_wh, stdout_rh, stderr_rh):
Guido van Rossum59691282013-10-30 14:52:03 -0700158 if h is not None:
159 _winapi.CloseHandle(h)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700160 raise
161 else:
162 if stdin_wh is not None:
163 self.stdin = PipeHandle(stdin_wh)
164 if stdout_rh is not None:
165 self.stdout = PipeHandle(stdout_rh)
166 if stderr_rh is not None:
167 self.stderr = PipeHandle(stderr_rh)
168 finally:
169 if stdin == PIPE:
170 os.close(stdin_rfd)
171 if stdout == PIPE:
172 os.close(stdout_wfd)
173 if stderr == PIPE:
174 os.close(stderr_wfd)