blob: ef277fac3e291c307d5e26ada8ef5e28c47434f1 [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 Stinnerfb2c3462019-01-10 11:24:40 +0100110 def __del__(self, _warn=warnings.warn):
Victor Stinner978a9af2015-01-29 17:50:58 +0100111 if self._handle is not None:
Victor Stinnerfb2c3462019-01-10 11:24:40 +0100112 _warn(f"unclosed {self!r}", ResourceWarning, source=self)
Victor Stinner978a9af2015-01-29 17:50:58 +0100113 self.close()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700114
115 def __enter__(self):
116 return self
117
118 def __exit__(self, t, v, tb):
119 self.close()
120
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700121
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700122# Replacement for subprocess.Popen using overlapped pipe handles
Guido van Rossuma8d630a2013-11-01 14:20:55 -0700123
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700124
125class Popen(subprocess.Popen):
126 """Replacement for subprocess.Popen using overlapped pipe handles.
127
128 The stdin, stdout, stderr are None or instances of PipeHandle.
129 """
130 def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
Guido van Rossum59691282013-10-30 14:52:03 -0700131 assert not kwds.get('universal_newlines')
132 assert kwds.get('bufsize', 0) == 0
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700133 stdin_rfd = stdout_wfd = stderr_wfd = None
134 stdin_wh = stdout_rh = stderr_rh = None
135 if stdin == PIPE:
Guido van Rossum59691282013-10-30 14:52:03 -0700136 stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700137 stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
Guido van Rossum59691282013-10-30 14:52:03 -0700138 else:
139 stdin_rfd = stdin
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700140 if stdout == PIPE:
141 stdout_rh, stdout_wh = pipe(overlapped=(True, False))
142 stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700143 else:
144 stdout_wfd = stdout
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700145 if stderr == PIPE:
146 stderr_rh, stderr_wh = pipe(overlapped=(True, False))
147 stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
Guido van Rossum59691282013-10-30 14:52:03 -0700148 elif stderr == STDOUT:
149 stderr_wfd = stdout_wfd
150 else:
151 stderr_wfd = stderr
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700152 try:
Guido van Rossum59691282013-10-30 14:52:03 -0700153 super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700154 stderr=stderr_wfd, **kwds)
155 except:
156 for h in (stdin_wh, stdout_rh, stderr_rh):
Guido van Rossum59691282013-10-30 14:52:03 -0700157 if h is not None:
158 _winapi.CloseHandle(h)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700159 raise
160 else:
161 if stdin_wh is not None:
162 self.stdin = PipeHandle(stdin_wh)
163 if stdout_rh is not None:
164 self.stdout = PipeHandle(stdout_rh)
165 if stderr_rh is not None:
166 self.stderr = PipeHandle(stderr_rh)
167 finally:
168 if stdin == PIPE:
169 os.close(stdin_rfd)
170 if stdout == PIPE:
171 os.close(stdout_wfd)
172 if stderr == PIPE:
173 os.close(stderr_wfd)