##// END OF EJS Templates
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
Manuel Jacob -
r45708:a37f290a default
parent child Browse files
Show More
@@ -1,752 +1,758 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import io
14 import io
15 import os
15 import os
16 import signal
16 import signal
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import threading
19 import threading
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23 from ..pycompat import (
23 from ..pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27
27
28 from .. import (
28 from .. import (
29 encoding,
29 encoding,
30 error,
30 error,
31 policy,
31 policy,
32 pycompat,
32 pycompat,
33 )
33 )
34
34
35 # Import like this to keep import-checker happy
35 # Import like this to keep import-checker happy
36 from ..utils import resourceutil
36 from ..utils import resourceutil
37
37
38 osutil = policy.importmod('osutil')
38 osutil = policy.importmod('osutil')
39
39
40 if pycompat.iswindows:
40 if pycompat.iswindows:
41 from .. import windows as platform
41 from .. import windows as platform
42 else:
42 else:
43 from .. import posix as platform
43 from .. import posix as platform
44
44
45
45
46 def isatty(fp):
46 def isatty(fp):
47 try:
47 try:
48 return fp.isatty()
48 return fp.isatty()
49 except AttributeError:
49 except AttributeError:
50 return False
50 return False
51
51
52
52
53 class LineBufferedWrapper(object):
53 class LineBufferedWrapper(object):
54 def __init__(self, orig):
54 def __init__(self, orig):
55 self.orig = orig
55 self.orig = orig
56
56
57 def __getattr__(self, attr):
57 def __getattr__(self, attr):
58 return getattr(self.orig, attr)
58 return getattr(self.orig, attr)
59
59
60 def write(self, s):
60 def write(self, s):
61 orig = self.orig
61 orig = self.orig
62 res = orig.write(s)
62 res = orig.write(s)
63 if s.endswith(b'\n'):
63 if s.endswith(b'\n'):
64 orig.flush()
64 orig.flush()
65 return res
65 return res
66
66
67
67
68 io.BufferedIOBase.register(LineBufferedWrapper)
68 io.BufferedIOBase.register(LineBufferedWrapper)
69
69
70
70
71 def make_line_buffered(stream):
71 def make_line_buffered(stream):
72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
73 # On Python 3, buffered streams can be expected to subclass
73 # On Python 3, buffered streams can be expected to subclass
74 # BufferedIOBase. This is definitively the case for the streams
74 # BufferedIOBase. This is definitively the case for the streams
75 # initialized by the interpreter. For unbuffered streams, we don't need
75 # initialized by the interpreter. For unbuffered streams, we don't need
76 # to emulate line buffering.
76 # to emulate line buffering.
77 return stream
77 return stream
78 if isinstance(stream, LineBufferedWrapper):
78 if isinstance(stream, LineBufferedWrapper):
79 return stream
79 return stream
80 return LineBufferedWrapper(stream)
80 return LineBufferedWrapper(stream)
81
81
82
82
83 class WriteAllWrapper(object):
83 class WriteAllWrapper(object):
84 def __init__(self, orig):
84 def __init__(self, orig):
85 self.orig = orig
85 self.orig = orig
86
86
87 def __getattr__(self, attr):
87 def __getattr__(self, attr):
88 return getattr(self.orig, attr)
88 return getattr(self.orig, attr)
89
89
90 def write(self, s):
90 def write(self, s):
91 write1 = self.orig.write
91 write1 = self.orig.write
92 m = memoryview(s)
92 m = memoryview(s)
93 total_to_write = len(s)
93 total_to_write = len(s)
94 total_written = 0
94 total_written = 0
95 while total_written < total_to_write:
95 while total_written < total_to_write:
96 total_written += write1(m[total_written:])
96 total_written += write1(m[total_written:])
97 return total_written
97 return total_written
98
98
99
99
100 io.IOBase.register(WriteAllWrapper)
100 io.IOBase.register(WriteAllWrapper)
101
101
102
102
103 def _make_write_all(stream):
103 def _make_write_all(stream):
104 assert pycompat.ispy3
104 assert pycompat.ispy3
105 if isinstance(stream, WriteAllWrapper):
105 if isinstance(stream, WriteAllWrapper):
106 return stream
106 return stream
107 if isinstance(stream, io.BufferedIOBase):
107 if isinstance(stream, io.BufferedIOBase):
108 # The io.BufferedIOBase.write() contract guarantees that all data is
108 # The io.BufferedIOBase.write() contract guarantees that all data is
109 # written.
109 # written.
110 return stream
110 return stream
111 # In general, the write() method of streams is free to write only part of
111 # In general, the write() method of streams is free to write only part of
112 # the data.
112 # the data.
113 return WriteAllWrapper(stream)
113 return WriteAllWrapper(stream)
114
114
115
115
116 if pycompat.ispy3:
116 if pycompat.ispy3:
117 # Python 3 implements its own I/O streams.
117 # Python 3 implements its own I/O streams.
118 # TODO: .buffer might not exist if std streams were replaced; we'll need
118 # TODO: .buffer might not exist if std streams were replaced; we'll need
119 # a silly wrapper to make a bytes stream backed by a unicode one.
119 # a silly wrapper to make a bytes stream backed by a unicode one.
120 stdin = sys.stdin.buffer
120 stdin = sys.stdin.buffer
121 stdout = _make_write_all(sys.stdout.buffer)
121 stdout = _make_write_all(sys.stdout.buffer)
122 stderr = _make_write_all(sys.stderr.buffer)
123 if pycompat.iswindows:
124 # Work around Windows bugs.
125 stdout = platform.winstdout(stdout)
126 stderr = platform.winstdout(stderr)
122 if isatty(stdout):
127 if isatty(stdout):
123 # The standard library doesn't offer line-buffered binary streams.
128 # The standard library doesn't offer line-buffered binary streams.
124 stdout = make_line_buffered(stdout)
129 stdout = make_line_buffered(stdout)
125 stderr = _make_write_all(sys.stderr.buffer)
126 else:
130 else:
127 # Python 2 uses the I/O streams provided by the C library.
131 # Python 2 uses the I/O streams provided by the C library.
128 stdin = sys.stdin
132 stdin = sys.stdin
129 stdout = sys.stdout
133 stdout = sys.stdout
134 stderr = sys.stderr
135 if pycompat.iswindows:
136 # Work around Windows bugs.
137 stdout = platform.winstdout(stdout)
138 stderr = platform.winstdout(stderr)
130 if isatty(stdout):
139 if isatty(stdout):
131 if pycompat.iswindows:
140 if pycompat.iswindows:
132 # Work around size limit when writing to console.
133 stdout = platform.winstdout(stdout)
134 # The Windows C runtime library doesn't support line buffering.
141 # The Windows C runtime library doesn't support line buffering.
135 stdout = make_line_buffered(stdout)
142 stdout = make_line_buffered(stdout)
136 else:
143 else:
137 # glibc determines buffering on first write to stdout - if we
144 # glibc determines buffering on first write to stdout - if we
138 # replace a TTY destined stdout with a pipe destined stdout (e.g.
145 # replace a TTY destined stdout with a pipe destined stdout (e.g.
139 # pager), we want line buffering.
146 # pager), we want line buffering.
140 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
147 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
141 stderr = sys.stderr
142
148
143
149
144 findexe = platform.findexe
150 findexe = platform.findexe
145 _gethgcmd = platform.gethgcmd
151 _gethgcmd = platform.gethgcmd
146 getuser = platform.getuser
152 getuser = platform.getuser
147 getpid = os.getpid
153 getpid = os.getpid
148 hidewindow = platform.hidewindow
154 hidewindow = platform.hidewindow
149 readpipe = platform.readpipe
155 readpipe = platform.readpipe
150 setbinary = platform.setbinary
156 setbinary = platform.setbinary
151 setsignalhandler = platform.setsignalhandler
157 setsignalhandler = platform.setsignalhandler
152 shellquote = platform.shellquote
158 shellquote = platform.shellquote
153 shellsplit = platform.shellsplit
159 shellsplit = platform.shellsplit
154 spawndetached = platform.spawndetached
160 spawndetached = platform.spawndetached
155 sshargs = platform.sshargs
161 sshargs = platform.sshargs
156 testpid = platform.testpid
162 testpid = platform.testpid
157
163
158 try:
164 try:
159 setprocname = osutil.setprocname
165 setprocname = osutil.setprocname
160 except AttributeError:
166 except AttributeError:
161 pass
167 pass
162 try:
168 try:
163 unblocksignal = osutil.unblocksignal
169 unblocksignal = osutil.unblocksignal
164 except AttributeError:
170 except AttributeError:
165 pass
171 pass
166
172
167 closefds = pycompat.isposix
173 closefds = pycompat.isposix
168
174
169
175
170 def explainexit(code):
176 def explainexit(code):
171 """return a message describing a subprocess status
177 """return a message describing a subprocess status
172 (codes from kill are negative - not os.system/wait encoding)"""
178 (codes from kill are negative - not os.system/wait encoding)"""
173 if code >= 0:
179 if code >= 0:
174 return _(b"exited with status %d") % code
180 return _(b"exited with status %d") % code
175 return _(b"killed by signal %d") % -code
181 return _(b"killed by signal %d") % -code
176
182
177
183
178 class _pfile(object):
184 class _pfile(object):
179 """File-like wrapper for a stream opened by subprocess.Popen()"""
185 """File-like wrapper for a stream opened by subprocess.Popen()"""
180
186
181 def __init__(self, proc, fp):
187 def __init__(self, proc, fp):
182 self._proc = proc
188 self._proc = proc
183 self._fp = fp
189 self._fp = fp
184
190
185 def close(self):
191 def close(self):
186 # unlike os.popen(), this returns an integer in subprocess coding
192 # unlike os.popen(), this returns an integer in subprocess coding
187 self._fp.close()
193 self._fp.close()
188 return self._proc.wait()
194 return self._proc.wait()
189
195
190 def __iter__(self):
196 def __iter__(self):
191 return iter(self._fp)
197 return iter(self._fp)
192
198
193 def __getattr__(self, attr):
199 def __getattr__(self, attr):
194 return getattr(self._fp, attr)
200 return getattr(self._fp, attr)
195
201
196 def __enter__(self):
202 def __enter__(self):
197 return self
203 return self
198
204
199 def __exit__(self, exc_type, exc_value, exc_tb):
205 def __exit__(self, exc_type, exc_value, exc_tb):
200 self.close()
206 self.close()
201
207
202
208
203 def popen(cmd, mode=b'rb', bufsize=-1):
209 def popen(cmd, mode=b'rb', bufsize=-1):
204 if mode == b'rb':
210 if mode == b'rb':
205 return _popenreader(cmd, bufsize)
211 return _popenreader(cmd, bufsize)
206 elif mode == b'wb':
212 elif mode == b'wb':
207 return _popenwriter(cmd, bufsize)
213 return _popenwriter(cmd, bufsize)
208 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
214 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
209
215
210
216
211 def _popenreader(cmd, bufsize):
217 def _popenreader(cmd, bufsize):
212 p = subprocess.Popen(
218 p = subprocess.Popen(
213 tonativestr(cmd),
219 tonativestr(cmd),
214 shell=True,
220 shell=True,
215 bufsize=bufsize,
221 bufsize=bufsize,
216 close_fds=closefds,
222 close_fds=closefds,
217 stdout=subprocess.PIPE,
223 stdout=subprocess.PIPE,
218 )
224 )
219 return _pfile(p, p.stdout)
225 return _pfile(p, p.stdout)
220
226
221
227
222 def _popenwriter(cmd, bufsize):
228 def _popenwriter(cmd, bufsize):
223 p = subprocess.Popen(
229 p = subprocess.Popen(
224 tonativestr(cmd),
230 tonativestr(cmd),
225 shell=True,
231 shell=True,
226 bufsize=bufsize,
232 bufsize=bufsize,
227 close_fds=closefds,
233 close_fds=closefds,
228 stdin=subprocess.PIPE,
234 stdin=subprocess.PIPE,
229 )
235 )
230 return _pfile(p, p.stdin)
236 return _pfile(p, p.stdin)
231
237
232
238
233 def popen2(cmd, env=None):
239 def popen2(cmd, env=None):
234 # Setting bufsize to -1 lets the system decide the buffer size.
240 # Setting bufsize to -1 lets the system decide the buffer size.
235 # The default for bufsize is 0, meaning unbuffered. This leads to
241 # The default for bufsize is 0, meaning unbuffered. This leads to
236 # poor performance on Mac OS X: http://bugs.python.org/issue4194
242 # poor performance on Mac OS X: http://bugs.python.org/issue4194
237 p = subprocess.Popen(
243 p = subprocess.Popen(
238 tonativestr(cmd),
244 tonativestr(cmd),
239 shell=True,
245 shell=True,
240 bufsize=-1,
246 bufsize=-1,
241 close_fds=closefds,
247 close_fds=closefds,
242 stdin=subprocess.PIPE,
248 stdin=subprocess.PIPE,
243 stdout=subprocess.PIPE,
249 stdout=subprocess.PIPE,
244 env=tonativeenv(env),
250 env=tonativeenv(env),
245 )
251 )
246 return p.stdin, p.stdout
252 return p.stdin, p.stdout
247
253
248
254
249 def popen3(cmd, env=None):
255 def popen3(cmd, env=None):
250 stdin, stdout, stderr, p = popen4(cmd, env)
256 stdin, stdout, stderr, p = popen4(cmd, env)
251 return stdin, stdout, stderr
257 return stdin, stdout, stderr
252
258
253
259
254 def popen4(cmd, env=None, bufsize=-1):
260 def popen4(cmd, env=None, bufsize=-1):
255 p = subprocess.Popen(
261 p = subprocess.Popen(
256 tonativestr(cmd),
262 tonativestr(cmd),
257 shell=True,
263 shell=True,
258 bufsize=bufsize,
264 bufsize=bufsize,
259 close_fds=closefds,
265 close_fds=closefds,
260 stdin=subprocess.PIPE,
266 stdin=subprocess.PIPE,
261 stdout=subprocess.PIPE,
267 stdout=subprocess.PIPE,
262 stderr=subprocess.PIPE,
268 stderr=subprocess.PIPE,
263 env=tonativeenv(env),
269 env=tonativeenv(env),
264 )
270 )
265 return p.stdin, p.stdout, p.stderr, p
271 return p.stdin, p.stdout, p.stderr, p
266
272
267
273
268 def pipefilter(s, cmd):
274 def pipefilter(s, cmd):
269 '''filter string S through command CMD, returning its output'''
275 '''filter string S through command CMD, returning its output'''
270 p = subprocess.Popen(
276 p = subprocess.Popen(
271 tonativestr(cmd),
277 tonativestr(cmd),
272 shell=True,
278 shell=True,
273 close_fds=closefds,
279 close_fds=closefds,
274 stdin=subprocess.PIPE,
280 stdin=subprocess.PIPE,
275 stdout=subprocess.PIPE,
281 stdout=subprocess.PIPE,
276 )
282 )
277 pout, perr = p.communicate(s)
283 pout, perr = p.communicate(s)
278 return pout
284 return pout
279
285
280
286
281 def tempfilter(s, cmd):
287 def tempfilter(s, cmd):
282 '''filter string S through a pair of temporary files with CMD.
288 '''filter string S through a pair of temporary files with CMD.
283 CMD is used as a template to create the real command to be run,
289 CMD is used as a template to create the real command to be run,
284 with the strings INFILE and OUTFILE replaced by the real names of
290 with the strings INFILE and OUTFILE replaced by the real names of
285 the temporary files generated.'''
291 the temporary files generated.'''
286 inname, outname = None, None
292 inname, outname = None, None
287 try:
293 try:
288 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
294 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
289 fp = os.fdopen(infd, 'wb')
295 fp = os.fdopen(infd, 'wb')
290 fp.write(s)
296 fp.write(s)
291 fp.close()
297 fp.close()
292 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
298 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
293 os.close(outfd)
299 os.close(outfd)
294 cmd = cmd.replace(b'INFILE', inname)
300 cmd = cmd.replace(b'INFILE', inname)
295 cmd = cmd.replace(b'OUTFILE', outname)
301 cmd = cmd.replace(b'OUTFILE', outname)
296 code = system(cmd)
302 code = system(cmd)
297 if pycompat.sysplatform == b'OpenVMS' and code & 1:
303 if pycompat.sysplatform == b'OpenVMS' and code & 1:
298 code = 0
304 code = 0
299 if code:
305 if code:
300 raise error.Abort(
306 raise error.Abort(
301 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
307 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
302 )
308 )
303 with open(outname, b'rb') as fp:
309 with open(outname, b'rb') as fp:
304 return fp.read()
310 return fp.read()
305 finally:
311 finally:
306 try:
312 try:
307 if inname:
313 if inname:
308 os.unlink(inname)
314 os.unlink(inname)
309 except OSError:
315 except OSError:
310 pass
316 pass
311 try:
317 try:
312 if outname:
318 if outname:
313 os.unlink(outname)
319 os.unlink(outname)
314 except OSError:
320 except OSError:
315 pass
321 pass
316
322
317
323
318 _filtertable = {
324 _filtertable = {
319 b'tempfile:': tempfilter,
325 b'tempfile:': tempfilter,
320 b'pipe:': pipefilter,
326 b'pipe:': pipefilter,
321 }
327 }
322
328
323
329
324 def filter(s, cmd):
330 def filter(s, cmd):
325 """filter a string through a command that transforms its input to its
331 """filter a string through a command that transforms its input to its
326 output"""
332 output"""
327 for name, fn in pycompat.iteritems(_filtertable):
333 for name, fn in pycompat.iteritems(_filtertable):
328 if cmd.startswith(name):
334 if cmd.startswith(name):
329 return fn(s, cmd[len(name) :].lstrip())
335 return fn(s, cmd[len(name) :].lstrip())
330 return pipefilter(s, cmd)
336 return pipefilter(s, cmd)
331
337
332
338
333 _hgexecutable = None
339 _hgexecutable = None
334
340
335
341
336 def hgexecutable():
342 def hgexecutable():
337 """return location of the 'hg' executable.
343 """return location of the 'hg' executable.
338
344
339 Defaults to $HG or 'hg' in the search path.
345 Defaults to $HG or 'hg' in the search path.
340 """
346 """
341 if _hgexecutable is None:
347 if _hgexecutable is None:
342 hg = encoding.environ.get(b'HG')
348 hg = encoding.environ.get(b'HG')
343 mainmod = sys.modules['__main__']
349 mainmod = sys.modules['__main__']
344 if hg:
350 if hg:
345 _sethgexecutable(hg)
351 _sethgexecutable(hg)
346 elif resourceutil.mainfrozen():
352 elif resourceutil.mainfrozen():
347 if getattr(sys, 'frozen', None) == 'macosx_app':
353 if getattr(sys, 'frozen', None) == 'macosx_app':
348 # Env variable set by py2app
354 # Env variable set by py2app
349 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
355 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
350 else:
356 else:
351 _sethgexecutable(pycompat.sysexecutable)
357 _sethgexecutable(pycompat.sysexecutable)
352 elif (
358 elif (
353 not pycompat.iswindows
359 not pycompat.iswindows
354 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
360 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
355 ):
361 ):
356 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
362 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
357 else:
363 else:
358 _sethgexecutable(
364 _sethgexecutable(
359 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
365 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
360 )
366 )
361 return _hgexecutable
367 return _hgexecutable
362
368
363
369
364 def _sethgexecutable(path):
370 def _sethgexecutable(path):
365 """set location of the 'hg' executable"""
371 """set location of the 'hg' executable"""
366 global _hgexecutable
372 global _hgexecutable
367 _hgexecutable = path
373 _hgexecutable = path
368
374
369
375
370 def _testfileno(f, stdf):
376 def _testfileno(f, stdf):
371 fileno = getattr(f, 'fileno', None)
377 fileno = getattr(f, 'fileno', None)
372 try:
378 try:
373 return fileno and fileno() == stdf.fileno()
379 return fileno and fileno() == stdf.fileno()
374 except io.UnsupportedOperation:
380 except io.UnsupportedOperation:
375 return False # fileno() raised UnsupportedOperation
381 return False # fileno() raised UnsupportedOperation
376
382
377
383
378 def isstdin(f):
384 def isstdin(f):
379 return _testfileno(f, sys.__stdin__)
385 return _testfileno(f, sys.__stdin__)
380
386
381
387
382 def isstdout(f):
388 def isstdout(f):
383 return _testfileno(f, sys.__stdout__)
389 return _testfileno(f, sys.__stdout__)
384
390
385
391
386 def protectstdio(uin, uout):
392 def protectstdio(uin, uout):
387 """Duplicate streams and redirect original if (uin, uout) are stdio
393 """Duplicate streams and redirect original if (uin, uout) are stdio
388
394
389 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
395 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
390 redirected to stderr so the output is still readable.
396 redirected to stderr so the output is still readable.
391
397
392 Returns (fin, fout) which point to the original (uin, uout) fds, but
398 Returns (fin, fout) which point to the original (uin, uout) fds, but
393 may be copy of (uin, uout). The returned streams can be considered
399 may be copy of (uin, uout). The returned streams can be considered
394 "owned" in that print(), exec(), etc. never reach to them.
400 "owned" in that print(), exec(), etc. never reach to them.
395 """
401 """
396 uout.flush()
402 uout.flush()
397 fin, fout = uin, uout
403 fin, fout = uin, uout
398 if _testfileno(uin, stdin):
404 if _testfileno(uin, stdin):
399 newfd = os.dup(uin.fileno())
405 newfd = os.dup(uin.fileno())
400 nullfd = os.open(os.devnull, os.O_RDONLY)
406 nullfd = os.open(os.devnull, os.O_RDONLY)
401 os.dup2(nullfd, uin.fileno())
407 os.dup2(nullfd, uin.fileno())
402 os.close(nullfd)
408 os.close(nullfd)
403 fin = os.fdopen(newfd, 'rb')
409 fin = os.fdopen(newfd, 'rb')
404 if _testfileno(uout, stdout):
410 if _testfileno(uout, stdout):
405 newfd = os.dup(uout.fileno())
411 newfd = os.dup(uout.fileno())
406 os.dup2(stderr.fileno(), uout.fileno())
412 os.dup2(stderr.fileno(), uout.fileno())
407 fout = os.fdopen(newfd, 'wb')
413 fout = os.fdopen(newfd, 'wb')
408 return fin, fout
414 return fin, fout
409
415
410
416
411 def restorestdio(uin, uout, fin, fout):
417 def restorestdio(uin, uout, fin, fout):
412 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
418 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
413 uout.flush()
419 uout.flush()
414 for f, uif in [(fin, uin), (fout, uout)]:
420 for f, uif in [(fin, uin), (fout, uout)]:
415 if f is not uif:
421 if f is not uif:
416 os.dup2(f.fileno(), uif.fileno())
422 os.dup2(f.fileno(), uif.fileno())
417 f.close()
423 f.close()
418
424
419
425
420 def shellenviron(environ=None):
426 def shellenviron(environ=None):
421 """return environ with optional override, useful for shelling out"""
427 """return environ with optional override, useful for shelling out"""
422
428
423 def py2shell(val):
429 def py2shell(val):
424 """convert python object into string that is useful to shell"""
430 """convert python object into string that is useful to shell"""
425 if val is None or val is False:
431 if val is None or val is False:
426 return b'0'
432 return b'0'
427 if val is True:
433 if val is True:
428 return b'1'
434 return b'1'
429 return pycompat.bytestr(val)
435 return pycompat.bytestr(val)
430
436
431 env = dict(encoding.environ)
437 env = dict(encoding.environ)
432 if environ:
438 if environ:
433 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
439 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
434 env[b'HG'] = hgexecutable()
440 env[b'HG'] = hgexecutable()
435 return env
441 return env
436
442
437
443
438 if pycompat.iswindows:
444 if pycompat.iswindows:
439
445
440 def shelltonative(cmd, env):
446 def shelltonative(cmd, env):
441 return platform.shelltocmdexe( # pytype: disable=module-attr
447 return platform.shelltocmdexe( # pytype: disable=module-attr
442 cmd, shellenviron(env)
448 cmd, shellenviron(env)
443 )
449 )
444
450
445 tonativestr = encoding.strfromlocal
451 tonativestr = encoding.strfromlocal
446 else:
452 else:
447
453
448 def shelltonative(cmd, env):
454 def shelltonative(cmd, env):
449 return cmd
455 return cmd
450
456
451 tonativestr = pycompat.identity
457 tonativestr = pycompat.identity
452
458
453
459
454 def tonativeenv(env):
460 def tonativeenv(env):
455 '''convert the environment from bytes to strings suitable for Popen(), etc.
461 '''convert the environment from bytes to strings suitable for Popen(), etc.
456 '''
462 '''
457 return pycompat.rapply(tonativestr, env)
463 return pycompat.rapply(tonativestr, env)
458
464
459
465
460 def system(cmd, environ=None, cwd=None, out=None):
466 def system(cmd, environ=None, cwd=None, out=None):
461 '''enhanced shell command execution.
467 '''enhanced shell command execution.
462 run with environment maybe modified, maybe in different dir.
468 run with environment maybe modified, maybe in different dir.
463
469
464 if out is specified, it is assumed to be a file-like object that has a
470 if out is specified, it is assumed to be a file-like object that has a
465 write() method. stdout and stderr will be redirected to out.'''
471 write() method. stdout and stderr will be redirected to out.'''
466 try:
472 try:
467 stdout.flush()
473 stdout.flush()
468 except Exception:
474 except Exception:
469 pass
475 pass
470 env = shellenviron(environ)
476 env = shellenviron(environ)
471 if out is None or isstdout(out):
477 if out is None or isstdout(out):
472 rc = subprocess.call(
478 rc = subprocess.call(
473 tonativestr(cmd),
479 tonativestr(cmd),
474 shell=True,
480 shell=True,
475 close_fds=closefds,
481 close_fds=closefds,
476 env=tonativeenv(env),
482 env=tonativeenv(env),
477 cwd=pycompat.rapply(tonativestr, cwd),
483 cwd=pycompat.rapply(tonativestr, cwd),
478 )
484 )
479 else:
485 else:
480 proc = subprocess.Popen(
486 proc = subprocess.Popen(
481 tonativestr(cmd),
487 tonativestr(cmd),
482 shell=True,
488 shell=True,
483 close_fds=closefds,
489 close_fds=closefds,
484 env=tonativeenv(env),
490 env=tonativeenv(env),
485 cwd=pycompat.rapply(tonativestr, cwd),
491 cwd=pycompat.rapply(tonativestr, cwd),
486 stdout=subprocess.PIPE,
492 stdout=subprocess.PIPE,
487 stderr=subprocess.STDOUT,
493 stderr=subprocess.STDOUT,
488 )
494 )
489 for line in iter(proc.stdout.readline, b''):
495 for line in iter(proc.stdout.readline, b''):
490 out.write(line)
496 out.write(line)
491 proc.wait()
497 proc.wait()
492 rc = proc.returncode
498 rc = proc.returncode
493 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
499 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
494 rc = 0
500 rc = 0
495 return rc
501 return rc
496
502
497
503
498 _is_gui = None
504 _is_gui = None
499
505
500
506
501 def _gui():
507 def _gui():
502 '''Are we running in a GUI?'''
508 '''Are we running in a GUI?'''
503 if pycompat.isdarwin:
509 if pycompat.isdarwin:
504 if b'SSH_CONNECTION' in encoding.environ:
510 if b'SSH_CONNECTION' in encoding.environ:
505 # handle SSH access to a box where the user is logged in
511 # handle SSH access to a box where the user is logged in
506 return False
512 return False
507 elif getattr(osutil, 'isgui', None):
513 elif getattr(osutil, 'isgui', None):
508 # check if a CoreGraphics session is available
514 # check if a CoreGraphics session is available
509 return osutil.isgui()
515 return osutil.isgui()
510 else:
516 else:
511 # pure build; use a safe default
517 # pure build; use a safe default
512 return True
518 return True
513 else:
519 else:
514 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
520 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
515
521
516
522
517 def gui():
523 def gui():
518 global _is_gui
524 global _is_gui
519 if _is_gui is None:
525 if _is_gui is None:
520 _is_gui = _gui()
526 _is_gui = _gui()
521 return _is_gui
527 return _is_gui
522
528
523
529
524 def hgcmd():
530 def hgcmd():
525 """Return the command used to execute current hg
531 """Return the command used to execute current hg
526
532
527 This is different from hgexecutable() because on Windows we want
533 This is different from hgexecutable() because on Windows we want
528 to avoid things opening new shell windows like batch files, so we
534 to avoid things opening new shell windows like batch files, so we
529 get either the python call or current executable.
535 get either the python call or current executable.
530 """
536 """
531 if resourceutil.mainfrozen():
537 if resourceutil.mainfrozen():
532 if getattr(sys, 'frozen', None) == 'macosx_app':
538 if getattr(sys, 'frozen', None) == 'macosx_app':
533 # Env variable set by py2app
539 # Env variable set by py2app
534 return [encoding.environ[b'EXECUTABLEPATH']]
540 return [encoding.environ[b'EXECUTABLEPATH']]
535 else:
541 else:
536 return [pycompat.sysexecutable]
542 return [pycompat.sysexecutable]
537 return _gethgcmd()
543 return _gethgcmd()
538
544
539
545
540 def rundetached(args, condfn):
546 def rundetached(args, condfn):
541 """Execute the argument list in a detached process.
547 """Execute the argument list in a detached process.
542
548
543 condfn is a callable which is called repeatedly and should return
549 condfn is a callable which is called repeatedly and should return
544 True once the child process is known to have started successfully.
550 True once the child process is known to have started successfully.
545 At this point, the child process PID is returned. If the child
551 At this point, the child process PID is returned. If the child
546 process fails to start or finishes before condfn() evaluates to
552 process fails to start or finishes before condfn() evaluates to
547 True, return -1.
553 True, return -1.
548 """
554 """
549 # Windows case is easier because the child process is either
555 # Windows case is easier because the child process is either
550 # successfully starting and validating the condition or exiting
556 # successfully starting and validating the condition or exiting
551 # on failure. We just poll on its PID. On Unix, if the child
557 # on failure. We just poll on its PID. On Unix, if the child
552 # process fails to start, it will be left in a zombie state until
558 # process fails to start, it will be left in a zombie state until
553 # the parent wait on it, which we cannot do since we expect a long
559 # the parent wait on it, which we cannot do since we expect a long
554 # running process on success. Instead we listen for SIGCHLD telling
560 # running process on success. Instead we listen for SIGCHLD telling
555 # us our child process terminated.
561 # us our child process terminated.
556 terminated = set()
562 terminated = set()
557
563
558 def handler(signum, frame):
564 def handler(signum, frame):
559 terminated.add(os.wait())
565 terminated.add(os.wait())
560
566
561 prevhandler = None
567 prevhandler = None
562 SIGCHLD = getattr(signal, 'SIGCHLD', None)
568 SIGCHLD = getattr(signal, 'SIGCHLD', None)
563 if SIGCHLD is not None:
569 if SIGCHLD is not None:
564 prevhandler = signal.signal(SIGCHLD, handler)
570 prevhandler = signal.signal(SIGCHLD, handler)
565 try:
571 try:
566 pid = spawndetached(args)
572 pid = spawndetached(args)
567 while not condfn():
573 while not condfn():
568 if (pid in terminated or not testpid(pid)) and not condfn():
574 if (pid in terminated or not testpid(pid)) and not condfn():
569 return -1
575 return -1
570 time.sleep(0.1)
576 time.sleep(0.1)
571 return pid
577 return pid
572 finally:
578 finally:
573 if prevhandler is not None:
579 if prevhandler is not None:
574 signal.signal(signal.SIGCHLD, prevhandler)
580 signal.signal(signal.SIGCHLD, prevhandler)
575
581
576
582
577 @contextlib.contextmanager
583 @contextlib.contextmanager
578 def uninterruptible(warn):
584 def uninterruptible(warn):
579 """Inhibit SIGINT handling on a region of code.
585 """Inhibit SIGINT handling on a region of code.
580
586
581 Note that if this is called in a non-main thread, it turns into a no-op.
587 Note that if this is called in a non-main thread, it turns into a no-op.
582
588
583 Args:
589 Args:
584 warn: A callable which takes no arguments, and returns True if the
590 warn: A callable which takes no arguments, and returns True if the
585 previous signal handling should be restored.
591 previous signal handling should be restored.
586 """
592 """
587
593
588 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
594 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
589 shouldbail = []
595 shouldbail = []
590
596
591 def disabledsiginthandler(*args):
597 def disabledsiginthandler(*args):
592 if warn():
598 if warn():
593 signal.signal(signal.SIGINT, oldsiginthandler[0])
599 signal.signal(signal.SIGINT, oldsiginthandler[0])
594 del oldsiginthandler[0]
600 del oldsiginthandler[0]
595 shouldbail.append(True)
601 shouldbail.append(True)
596
602
597 try:
603 try:
598 try:
604 try:
599 signal.signal(signal.SIGINT, disabledsiginthandler)
605 signal.signal(signal.SIGINT, disabledsiginthandler)
600 except ValueError:
606 except ValueError:
601 # wrong thread, oh well, we tried
607 # wrong thread, oh well, we tried
602 del oldsiginthandler[0]
608 del oldsiginthandler[0]
603 yield
609 yield
604 finally:
610 finally:
605 if oldsiginthandler:
611 if oldsiginthandler:
606 signal.signal(signal.SIGINT, oldsiginthandler[0])
612 signal.signal(signal.SIGINT, oldsiginthandler[0])
607 if shouldbail:
613 if shouldbail:
608 raise KeyboardInterrupt
614 raise KeyboardInterrupt
609
615
610
616
611 if pycompat.iswindows:
617 if pycompat.iswindows:
612 # no fork on Windows, but we can create a detached process
618 # no fork on Windows, but we can create a detached process
613 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
619 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
614 # No stdlib constant exists for this value
620 # No stdlib constant exists for this value
615 DETACHED_PROCESS = 0x00000008
621 DETACHED_PROCESS = 0x00000008
616 # Following creation flags might create a console GUI window.
622 # Following creation flags might create a console GUI window.
617 # Using subprocess.CREATE_NEW_CONSOLE might helps.
623 # Using subprocess.CREATE_NEW_CONSOLE might helps.
618 # See https://phab.mercurial-scm.org/D1701 for discussion
624 # See https://phab.mercurial-scm.org/D1701 for discussion
619 _creationflags = (
625 _creationflags = (
620 DETACHED_PROCESS
626 DETACHED_PROCESS
621 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
627 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
622 )
628 )
623
629
624 def runbgcommand(
630 def runbgcommand(
625 script,
631 script,
626 env,
632 env,
627 shell=False,
633 shell=False,
628 stdout=None,
634 stdout=None,
629 stderr=None,
635 stderr=None,
630 ensurestart=True,
636 ensurestart=True,
631 record_wait=None,
637 record_wait=None,
632 ):
638 ):
633 '''Spawn a command without waiting for it to finish.'''
639 '''Spawn a command without waiting for it to finish.'''
634 # we can't use close_fds *and* redirect stdin. I'm not sure that we
640 # we can't use close_fds *and* redirect stdin. I'm not sure that we
635 # need to because the detached process has no console connection.
641 # need to because the detached process has no console connection.
636 p = subprocess.Popen(
642 p = subprocess.Popen(
637 tonativestr(script),
643 tonativestr(script),
638 shell=shell,
644 shell=shell,
639 env=tonativeenv(env),
645 env=tonativeenv(env),
640 close_fds=True,
646 close_fds=True,
641 creationflags=_creationflags,
647 creationflags=_creationflags,
642 stdout=stdout,
648 stdout=stdout,
643 stderr=stderr,
649 stderr=stderr,
644 )
650 )
645 if record_wait is not None:
651 if record_wait is not None:
646 record_wait(p.wait)
652 record_wait(p.wait)
647
653
648
654
649 else:
655 else:
650
656
651 def runbgcommand(
657 def runbgcommand(
652 cmd,
658 cmd,
653 env,
659 env,
654 shell=False,
660 shell=False,
655 stdout=None,
661 stdout=None,
656 stderr=None,
662 stderr=None,
657 ensurestart=True,
663 ensurestart=True,
658 record_wait=None,
664 record_wait=None,
659 ):
665 ):
660 '''Spawn a command without waiting for it to finish.
666 '''Spawn a command without waiting for it to finish.
661
667
662
668
663 When `record_wait` is not None, the spawned process will not be fully
669 When `record_wait` is not None, the spawned process will not be fully
664 detached and the `record_wait` argument will be called with a the
670 detached and the `record_wait` argument will be called with a the
665 `Subprocess.wait` function for the spawned process. This is mostly
671 `Subprocess.wait` function for the spawned process. This is mostly
666 useful for developers that need to make sure the spawned process
672 useful for developers that need to make sure the spawned process
667 finished before a certain point. (eg: writing test)'''
673 finished before a certain point. (eg: writing test)'''
668 if pycompat.isdarwin:
674 if pycompat.isdarwin:
669 # avoid crash in CoreFoundation in case another thread
675 # avoid crash in CoreFoundation in case another thread
670 # calls gui() while we're calling fork().
676 # calls gui() while we're calling fork().
671 gui()
677 gui()
672
678
673 # double-fork to completely detach from the parent process
679 # double-fork to completely detach from the parent process
674 # based on http://code.activestate.com/recipes/278731
680 # based on http://code.activestate.com/recipes/278731
675 if record_wait is None:
681 if record_wait is None:
676 pid = os.fork()
682 pid = os.fork()
677 if pid:
683 if pid:
678 if not ensurestart:
684 if not ensurestart:
679 # Even though we're not waiting on the child process,
685 # Even though we're not waiting on the child process,
680 # we still must call waitpid() on it at some point so
686 # we still must call waitpid() on it at some point so
681 # it's not a zombie/defunct. This is especially relevant for
687 # it's not a zombie/defunct. This is especially relevant for
682 # chg since the parent process won't die anytime soon.
688 # chg since the parent process won't die anytime soon.
683 # We use a thread to make the overhead tiny.
689 # We use a thread to make the overhead tiny.
684 def _do_wait():
690 def _do_wait():
685 os.waitpid(pid, 0)
691 os.waitpid(pid, 0)
686
692
687 t = threading.Thread(target=_do_wait)
693 t = threading.Thread(target=_do_wait)
688 t.daemon = True
694 t.daemon = True
689 t.start()
695 t.start()
690 return
696 return
691 # Parent process
697 # Parent process
692 (_pid, status) = os.waitpid(pid, 0)
698 (_pid, status) = os.waitpid(pid, 0)
693 if os.WIFEXITED(status):
699 if os.WIFEXITED(status):
694 returncode = os.WEXITSTATUS(status)
700 returncode = os.WEXITSTATUS(status)
695 else:
701 else:
696 returncode = -(os.WTERMSIG(status))
702 returncode = -(os.WTERMSIG(status))
697 if returncode != 0:
703 if returncode != 0:
698 # The child process's return code is 0 on success, an errno
704 # The child process's return code is 0 on success, an errno
699 # value on failure, or 255 if we don't have a valid errno
705 # value on failure, or 255 if we don't have a valid errno
700 # value.
706 # value.
701 #
707 #
702 # (It would be slightly nicer to return the full exception info
708 # (It would be slightly nicer to return the full exception info
703 # over a pipe as the subprocess module does. For now it
709 # over a pipe as the subprocess module does. For now it
704 # doesn't seem worth adding that complexity here, though.)
710 # doesn't seem worth adding that complexity here, though.)
705 if returncode == 255:
711 if returncode == 255:
706 returncode = errno.EINVAL
712 returncode = errno.EINVAL
707 raise OSError(
713 raise OSError(
708 returncode,
714 returncode,
709 b'error running %r: %s'
715 b'error running %r: %s'
710 % (cmd, os.strerror(returncode)),
716 % (cmd, os.strerror(returncode)),
711 )
717 )
712 return
718 return
713
719
714 returncode = 255
720 returncode = 255
715 try:
721 try:
716 if record_wait is None:
722 if record_wait is None:
717 # Start a new session
723 # Start a new session
718 os.setsid()
724 os.setsid()
719
725
720 stdin = open(os.devnull, b'r')
726 stdin = open(os.devnull, b'r')
721 if stdout is None:
727 if stdout is None:
722 stdout = open(os.devnull, b'w')
728 stdout = open(os.devnull, b'w')
723 if stderr is None:
729 if stderr is None:
724 stderr = open(os.devnull, b'w')
730 stderr = open(os.devnull, b'w')
725
731
726 # connect stdin to devnull to make sure the subprocess can't
732 # connect stdin to devnull to make sure the subprocess can't
727 # muck up that stream for mercurial.
733 # muck up that stream for mercurial.
728 p = subprocess.Popen(
734 p = subprocess.Popen(
729 cmd,
735 cmd,
730 shell=shell,
736 shell=shell,
731 env=env,
737 env=env,
732 close_fds=True,
738 close_fds=True,
733 stdin=stdin,
739 stdin=stdin,
734 stdout=stdout,
740 stdout=stdout,
735 stderr=stderr,
741 stderr=stderr,
736 )
742 )
737 if record_wait is not None:
743 if record_wait is not None:
738 record_wait(p.wait)
744 record_wait(p.wait)
739 returncode = 0
745 returncode = 0
740 except EnvironmentError as ex:
746 except EnvironmentError as ex:
741 returncode = ex.errno & 0xFF
747 returncode = ex.errno & 0xFF
742 if returncode == 0:
748 if returncode == 0:
743 # This shouldn't happen, but just in case make sure the
749 # This shouldn't happen, but just in case make sure the
744 # return code is never 0 here.
750 # return code is never 0 here.
745 returncode = 255
751 returncode = 255
746 except Exception:
752 except Exception:
747 returncode = 255
753 returncode = 255
748 finally:
754 finally:
749 # mission accomplished, this child needs to exit and not
755 # mission accomplished, this child needs to exit and not
750 # continue the hg process here.
756 # continue the hg process here.
751 if record_wait is None:
757 if record_wait is None:
752 os._exit(returncode)
758 os._exit(returncode)
@@ -1,681 +1,685 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import getpass
11 import getpass
12 import msvcrt
12 import msvcrt
13 import os
13 import os
14 import re
14 import re
15 import stat
15 import stat
16 import string
16 import string
17 import sys
17 import sys
18
18
19 from .i18n import _
19 from .i18n import _
20 from .pycompat import getattr
20 from .pycompat import getattr
21 from . import (
21 from . import (
22 encoding,
22 encoding,
23 error,
23 error,
24 policy,
24 policy,
25 pycompat,
25 pycompat,
26 win32,
26 win32,
27 )
27 )
28
28
29 try:
29 try:
30 import _winreg as winreg # pytype: disable=import-error
30 import _winreg as winreg # pytype: disable=import-error
31
31
32 winreg.CloseKey
32 winreg.CloseKey
33 except ImportError:
33 except ImportError:
34 # py2 only
34 # py2 only
35 import winreg # pytype: disable=import-error
35 import winreg # pytype: disable=import-error
36
36
37 osutil = policy.importmod('osutil')
37 osutil = policy.importmod('osutil')
38
38
39 getfsmountpoint = win32.getvolumename
39 getfsmountpoint = win32.getvolumename
40 getfstype = win32.getfstype
40 getfstype = win32.getfstype
41 getuser = win32.getuser
41 getuser = win32.getuser
42 hidewindow = win32.hidewindow
42 hidewindow = win32.hidewindow
43 makedir = win32.makedir
43 makedir = win32.makedir
44 nlinks = win32.nlinks
44 nlinks = win32.nlinks
45 oslink = win32.oslink
45 oslink = win32.oslink
46 samedevice = win32.samedevice
46 samedevice = win32.samedevice
47 samefile = win32.samefile
47 samefile = win32.samefile
48 setsignalhandler = win32.setsignalhandler
48 setsignalhandler = win32.setsignalhandler
49 spawndetached = win32.spawndetached
49 spawndetached = win32.spawndetached
50 split = os.path.split
50 split = os.path.split
51 testpid = win32.testpid
51 testpid = win32.testpid
52 unlink = win32.unlink
52 unlink = win32.unlink
53
53
54 umask = 0o022
54 umask = 0o022
55
55
56
56
57 class mixedfilemodewrapper(object):
57 class mixedfilemodewrapper(object):
58 """Wraps a file handle when it is opened in read/write mode.
58 """Wraps a file handle when it is opened in read/write mode.
59
59
60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
61 that files opened with mode r+, w+, or a+ make a call to a file positioning
61 that files opened with mode r+, w+, or a+ make a call to a file positioning
62 function when switching between reads and writes. Without this extra call,
62 function when switching between reads and writes. Without this extra call,
63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
64
64
65 This class wraps posixfile instances when the file is opened in read/write
65 This class wraps posixfile instances when the file is opened in read/write
66 mode and automatically adds checks or inserts appropriate file positioning
66 mode and automatically adds checks or inserts appropriate file positioning
67 calls when necessary.
67 calls when necessary.
68 """
68 """
69
69
70 OPNONE = 0
70 OPNONE = 0
71 OPREAD = 1
71 OPREAD = 1
72 OPWRITE = 2
72 OPWRITE = 2
73
73
74 def __init__(self, fp):
74 def __init__(self, fp):
75 object.__setattr__(self, '_fp', fp)
75 object.__setattr__(self, '_fp', fp)
76 object.__setattr__(self, '_lastop', 0)
76 object.__setattr__(self, '_lastop', 0)
77
77
78 def __enter__(self):
78 def __enter__(self):
79 self._fp.__enter__()
79 self._fp.__enter__()
80 return self
80 return self
81
81
82 def __exit__(self, exc_type, exc_val, exc_tb):
82 def __exit__(self, exc_type, exc_val, exc_tb):
83 self._fp.__exit__(exc_type, exc_val, exc_tb)
83 self._fp.__exit__(exc_type, exc_val, exc_tb)
84
84
85 def __getattr__(self, name):
85 def __getattr__(self, name):
86 return getattr(self._fp, name)
86 return getattr(self._fp, name)
87
87
88 def __setattr__(self, name, value):
88 def __setattr__(self, name, value):
89 return self._fp.__setattr__(name, value)
89 return self._fp.__setattr__(name, value)
90
90
91 def _noopseek(self):
91 def _noopseek(self):
92 self._fp.seek(0, os.SEEK_CUR)
92 self._fp.seek(0, os.SEEK_CUR)
93
93
94 def seek(self, *args, **kwargs):
94 def seek(self, *args, **kwargs):
95 object.__setattr__(self, '_lastop', self.OPNONE)
95 object.__setattr__(self, '_lastop', self.OPNONE)
96 return self._fp.seek(*args, **kwargs)
96 return self._fp.seek(*args, **kwargs)
97
97
98 def write(self, d):
98 def write(self, d):
99 if self._lastop == self.OPREAD:
99 if self._lastop == self.OPREAD:
100 self._noopseek()
100 self._noopseek()
101
101
102 object.__setattr__(self, '_lastop', self.OPWRITE)
102 object.__setattr__(self, '_lastop', self.OPWRITE)
103 return self._fp.write(d)
103 return self._fp.write(d)
104
104
105 def writelines(self, *args, **kwargs):
105 def writelines(self, *args, **kwargs):
106 if self._lastop == self.OPREAD:
106 if self._lastop == self.OPREAD:
107 self._noopeseek()
107 self._noopeseek()
108
108
109 object.__setattr__(self, '_lastop', self.OPWRITE)
109 object.__setattr__(self, '_lastop', self.OPWRITE)
110 return self._fp.writelines(*args, **kwargs)
110 return self._fp.writelines(*args, **kwargs)
111
111
112 def read(self, *args, **kwargs):
112 def read(self, *args, **kwargs):
113 if self._lastop == self.OPWRITE:
113 if self._lastop == self.OPWRITE:
114 self._noopseek()
114 self._noopseek()
115
115
116 object.__setattr__(self, '_lastop', self.OPREAD)
116 object.__setattr__(self, '_lastop', self.OPREAD)
117 return self._fp.read(*args, **kwargs)
117 return self._fp.read(*args, **kwargs)
118
118
119 def readline(self, *args, **kwargs):
119 def readline(self, *args, **kwargs):
120 if self._lastop == self.OPWRITE:
120 if self._lastop == self.OPWRITE:
121 self._noopseek()
121 self._noopseek()
122
122
123 object.__setattr__(self, '_lastop', self.OPREAD)
123 object.__setattr__(self, '_lastop', self.OPREAD)
124 return self._fp.readline(*args, **kwargs)
124 return self._fp.readline(*args, **kwargs)
125
125
126 def readlines(self, *args, **kwargs):
126 def readlines(self, *args, **kwargs):
127 if self._lastop == self.OPWRITE:
127 if self._lastop == self.OPWRITE:
128 self._noopseek()
128 self._noopseek()
129
129
130 object.__setattr__(self, '_lastop', self.OPREAD)
130 object.__setattr__(self, '_lastop', self.OPREAD)
131 return self._fp.readlines(*args, **kwargs)
131 return self._fp.readlines(*args, **kwargs)
132
132
133
133
134 class fdproxy(object):
134 class fdproxy(object):
135 """Wraps osutil.posixfile() to override the name attribute to reflect the
135 """Wraps osutil.posixfile() to override the name attribute to reflect the
136 underlying file name.
136 underlying file name.
137 """
137 """
138
138
139 def __init__(self, name, fp):
139 def __init__(self, name, fp):
140 self.name = name
140 self.name = name
141 self._fp = fp
141 self._fp = fp
142
142
143 def __enter__(self):
143 def __enter__(self):
144 self._fp.__enter__()
144 self._fp.__enter__()
145 # Return this wrapper for the context manager so that the name is
145 # Return this wrapper for the context manager so that the name is
146 # still available.
146 # still available.
147 return self
147 return self
148
148
149 def __exit__(self, exc_type, exc_value, traceback):
149 def __exit__(self, exc_type, exc_value, traceback):
150 self._fp.__exit__(exc_type, exc_value, traceback)
150 self._fp.__exit__(exc_type, exc_value, traceback)
151
151
152 def __iter__(self):
152 def __iter__(self):
153 return iter(self._fp)
153 return iter(self._fp)
154
154
155 def __getattr__(self, name):
155 def __getattr__(self, name):
156 return getattr(self._fp, name)
156 return getattr(self._fp, name)
157
157
158
158
159 def posixfile(name, mode=b'r', buffering=-1):
159 def posixfile(name, mode=b'r', buffering=-1):
160 '''Open a file with even more POSIX-like semantics'''
160 '''Open a file with even more POSIX-like semantics'''
161 try:
161 try:
162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
163
163
164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
165 # underlying file descriptor.
165 # underlying file descriptor.
166 if pycompat.ispy3:
166 if pycompat.ispy3:
167 fp = fdproxy(name, fp)
167 fp = fdproxy(name, fp)
168
168
169 # The position when opening in append mode is implementation defined, so
169 # The position when opening in append mode is implementation defined, so
170 # make it consistent with other platforms, which position at EOF.
170 # make it consistent with other platforms, which position at EOF.
171 if b'a' in mode:
171 if b'a' in mode:
172 fp.seek(0, os.SEEK_END)
172 fp.seek(0, os.SEEK_END)
173
173
174 if b'+' in mode:
174 if b'+' in mode:
175 return mixedfilemodewrapper(fp)
175 return mixedfilemodewrapper(fp)
176
176
177 return fp
177 return fp
178 except WindowsError as err:
178 except WindowsError as err:
179 # convert to a friendlier exception
179 # convert to a friendlier exception
180 raise IOError(
180 raise IOError(
181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
182 )
182 )
183
183
184
184
185 # may be wrapped by win32mbcs extension
185 # may be wrapped by win32mbcs extension
186 listdir = osutil.listdir
186 listdir = osutil.listdir
187
187
188
188
189 class winstdout(object):
189 class winstdout(object):
190 '''Some files on Windows misbehave.
190 '''Some files on Windows misbehave.
191
191
192 When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
192 When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
193
193
194 When writing too many bytes to a console at the same, a "Not enough space"
194 When writing too many bytes to a console at the same, a "Not enough space"
195 error may happen. Python 3 already works around that.
195 error may happen. Python 3 already works around that.
196 '''
196 '''
197
197
198 def __init__(self, fp):
198 def __init__(self, fp):
199 self.fp = fp
199 self.fp = fp
200 self.throttle = not pycompat.ispy3 and fp.isatty()
200
201
201 def __getattr__(self, key):
202 def __getattr__(self, key):
202 return getattr(self.fp, key)
203 return getattr(self.fp, key)
203
204
204 def close(self):
205 def close(self):
205 try:
206 try:
206 self.fp.close()
207 self.fp.close()
207 except IOError:
208 except IOError:
208 pass
209 pass
209
210
210 def write(self, s):
211 def write(self, s):
212 if not pycompat.ispy3:
213 self.softspace = 0
211 try:
214 try:
215 if not self.throttle:
216 return self.fp.write(s)
212 # This is workaround for "Not enough space" error on
217 # This is workaround for "Not enough space" error on
213 # writing large size of data to console.
218 # writing large size of data to console.
214 limit = 16000
219 limit = 16000
215 l = len(s)
220 l = len(s)
216 start = 0
221 start = 0
217 self.softspace = 0
218 while start < l:
222 while start < l:
219 end = start + limit
223 end = start + limit
220 self.fp.write(s[start:end])
224 self.fp.write(s[start:end])
221 start = end
225 start = end
222 except IOError as inst:
226 except IOError as inst:
223 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
227 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
224 raise
228 raise
225 self.close()
229 self.close()
226 raise IOError(errno.EPIPE, 'Broken pipe')
230 raise IOError(errno.EPIPE, 'Broken pipe')
227
231
228 def flush(self):
232 def flush(self):
229 try:
233 try:
230 return self.fp.flush()
234 return self.fp.flush()
231 except IOError as inst:
235 except IOError as inst:
232 if not win32.lasterrorwaspipeerror(inst):
236 if not win32.lasterrorwaspipeerror(inst):
233 raise
237 raise
234 raise IOError(errno.EPIPE, 'Broken pipe')
238 raise IOError(errno.EPIPE, 'Broken pipe')
235
239
236
240
237 def openhardlinks():
241 def openhardlinks():
238 return True
242 return True
239
243
240
244
241 def parsepatchoutput(output_line):
245 def parsepatchoutput(output_line):
242 """parses the output produced by patch and returns the filename"""
246 """parses the output produced by patch and returns the filename"""
243 pf = output_line[14:]
247 pf = output_line[14:]
244 if pf[0] == b'`':
248 if pf[0] == b'`':
245 pf = pf[1:-1] # Remove the quotes
249 pf = pf[1:-1] # Remove the quotes
246 return pf
250 return pf
247
251
248
252
249 def sshargs(sshcmd, host, user, port):
253 def sshargs(sshcmd, host, user, port):
250 '''Build argument list for ssh or Plink'''
254 '''Build argument list for ssh or Plink'''
251 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
255 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
252 args = user and (b"%s@%s" % (user, host)) or host
256 args = user and (b"%s@%s" % (user, host)) or host
253 if args.startswith(b'-') or args.startswith(b'/'):
257 if args.startswith(b'-') or args.startswith(b'/'):
254 raise error.Abort(
258 raise error.Abort(
255 _(b'illegal ssh hostname or username starting with - or /: %s')
259 _(b'illegal ssh hostname or username starting with - or /: %s')
256 % args
260 % args
257 )
261 )
258 args = shellquote(args)
262 args = shellquote(args)
259 if port:
263 if port:
260 args = b'%s %s %s' % (pflag, shellquote(port), args)
264 args = b'%s %s %s' % (pflag, shellquote(port), args)
261 return args
265 return args
262
266
263
267
264 def setflags(f, l, x):
268 def setflags(f, l, x):
265 pass
269 pass
266
270
267
271
268 def copymode(src, dst, mode=None, enforcewritable=False):
272 def copymode(src, dst, mode=None, enforcewritable=False):
269 pass
273 pass
270
274
271
275
272 def checkexec(path):
276 def checkexec(path):
273 return False
277 return False
274
278
275
279
276 def checklink(path):
280 def checklink(path):
277 return False
281 return False
278
282
279
283
280 def setbinary(fd):
284 def setbinary(fd):
281 # When run without console, pipes may expose invalid
285 # When run without console, pipes may expose invalid
282 # fileno(), usually set to -1.
286 # fileno(), usually set to -1.
283 fno = getattr(fd, 'fileno', None)
287 fno = getattr(fd, 'fileno', None)
284 if fno is not None and fno() >= 0:
288 if fno is not None and fno() >= 0:
285 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
289 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
286
290
287
291
288 def pconvert(path):
292 def pconvert(path):
289 return path.replace(pycompat.ossep, b'/')
293 return path.replace(pycompat.ossep, b'/')
290
294
291
295
292 def localpath(path):
296 def localpath(path):
293 return path.replace(b'/', b'\\')
297 return path.replace(b'/', b'\\')
294
298
295
299
296 def normpath(path):
300 def normpath(path):
297 return pconvert(os.path.normpath(path))
301 return pconvert(os.path.normpath(path))
298
302
299
303
300 def normcase(path):
304 def normcase(path):
301 return encoding.upper(path) # NTFS compares via upper()
305 return encoding.upper(path) # NTFS compares via upper()
302
306
303
307
304 # see posix.py for definitions
308 # see posix.py for definitions
305 normcasespec = encoding.normcasespecs.upper
309 normcasespec = encoding.normcasespecs.upper
306 normcasefallback = encoding.upperfallback
310 normcasefallback = encoding.upperfallback
307
311
308
312
309 def samestat(s1, s2):
313 def samestat(s1, s2):
310 return False
314 return False
311
315
312
316
313 def shelltocmdexe(path, env):
317 def shelltocmdexe(path, env):
314 r"""Convert shell variables in the form $var and ${var} inside ``path``
318 r"""Convert shell variables in the form $var and ${var} inside ``path``
315 to %var% form. Existing Windows style variables are left unchanged.
319 to %var% form. Existing Windows style variables are left unchanged.
316
320
317 The variables are limited to the given environment. Unknown variables are
321 The variables are limited to the given environment. Unknown variables are
318 left unchanged.
322 left unchanged.
319
323
320 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
324 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
321 >>> # Only valid values are expanded
325 >>> # Only valid values are expanded
322 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
326 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
323 ... e)
327 ... e)
324 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
328 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
325 >>> # Single quote prevents expansion, as does \$ escaping
329 >>> # Single quote prevents expansion, as does \$ escaping
326 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
330 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
327 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
331 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
328 >>> # $$ is not special. %% is not special either, but can be the end and
332 >>> # $$ is not special. %% is not special either, but can be the end and
329 >>> # start of consecutive variables
333 >>> # start of consecutive variables
330 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
334 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
331 'cmd $$ %% %var1%%var2%'
335 'cmd $$ %% %var1%%var2%'
332 >>> # No double substitution
336 >>> # No double substitution
333 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
337 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
334 '%var1% %var1%'
338 '%var1% %var1%'
335 >>> # Tilde expansion
339 >>> # Tilde expansion
336 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
340 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
337 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
341 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
338 """
342 """
339 if not any(c in path for c in b"$'~"):
343 if not any(c in path for c in b"$'~"):
340 return path
344 return path
341
345
342 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
346 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
343
347
344 res = b''
348 res = b''
345 index = 0
349 index = 0
346 pathlen = len(path)
350 pathlen = len(path)
347 while index < pathlen:
351 while index < pathlen:
348 c = path[index : index + 1]
352 c = path[index : index + 1]
349 if c == b'\'': # no expansion within single quotes
353 if c == b'\'': # no expansion within single quotes
350 path = path[index + 1 :]
354 path = path[index + 1 :]
351 pathlen = len(path)
355 pathlen = len(path)
352 try:
356 try:
353 index = path.index(b'\'')
357 index = path.index(b'\'')
354 res += b'"' + path[:index] + b'"'
358 res += b'"' + path[:index] + b'"'
355 except ValueError:
359 except ValueError:
356 res += c + path
360 res += c + path
357 index = pathlen - 1
361 index = pathlen - 1
358 elif c == b'%': # variable
362 elif c == b'%': # variable
359 path = path[index + 1 :]
363 path = path[index + 1 :]
360 pathlen = len(path)
364 pathlen = len(path)
361 try:
365 try:
362 index = path.index(b'%')
366 index = path.index(b'%')
363 except ValueError:
367 except ValueError:
364 res += b'%' + path
368 res += b'%' + path
365 index = pathlen - 1
369 index = pathlen - 1
366 else:
370 else:
367 var = path[:index]
371 var = path[:index]
368 res += b'%' + var + b'%'
372 res += b'%' + var + b'%'
369 elif c == b'$': # variable
373 elif c == b'$': # variable
370 if path[index + 1 : index + 2] == b'{':
374 if path[index + 1 : index + 2] == b'{':
371 path = path[index + 2 :]
375 path = path[index + 2 :]
372 pathlen = len(path)
376 pathlen = len(path)
373 try:
377 try:
374 index = path.index(b'}')
378 index = path.index(b'}')
375 var = path[:index]
379 var = path[:index]
376
380
377 # See below for why empty variables are handled specially
381 # See below for why empty variables are handled specially
378 if env.get(var, b'') != b'':
382 if env.get(var, b'') != b'':
379 res += b'%' + var + b'%'
383 res += b'%' + var + b'%'
380 else:
384 else:
381 res += b'${' + var + b'}'
385 res += b'${' + var + b'}'
382 except ValueError:
386 except ValueError:
383 res += b'${' + path
387 res += b'${' + path
384 index = pathlen - 1
388 index = pathlen - 1
385 else:
389 else:
386 var = b''
390 var = b''
387 index += 1
391 index += 1
388 c = path[index : index + 1]
392 c = path[index : index + 1]
389 while c != b'' and c in varchars:
393 while c != b'' and c in varchars:
390 var += c
394 var += c
391 index += 1
395 index += 1
392 c = path[index : index + 1]
396 c = path[index : index + 1]
393 # Some variables (like HG_OLDNODE) may be defined, but have an
397 # Some variables (like HG_OLDNODE) may be defined, but have an
394 # empty value. Those need to be skipped because when spawning
398 # empty value. Those need to be skipped because when spawning
395 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
399 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
396 # VAR, and that really confuses things like revset expressions.
400 # VAR, and that really confuses things like revset expressions.
397 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
401 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
398 # will substitute to an empty string, and everything is happy.
402 # will substitute to an empty string, and everything is happy.
399 if env.get(var, b'') != b'':
403 if env.get(var, b'') != b'':
400 res += b'%' + var + b'%'
404 res += b'%' + var + b'%'
401 else:
405 else:
402 res += b'$' + var
406 res += b'$' + var
403
407
404 if c != b'':
408 if c != b'':
405 index -= 1
409 index -= 1
406 elif (
410 elif (
407 c == b'~'
411 c == b'~'
408 and index + 1 < pathlen
412 and index + 1 < pathlen
409 and path[index + 1 : index + 2] in (b'\\', b'/')
413 and path[index + 1 : index + 2] in (b'\\', b'/')
410 ):
414 ):
411 res += b"%USERPROFILE%"
415 res += b"%USERPROFILE%"
412 elif (
416 elif (
413 c == b'\\'
417 c == b'\\'
414 and index + 1 < pathlen
418 and index + 1 < pathlen
415 and path[index + 1 : index + 2] in (b'$', b'~')
419 and path[index + 1 : index + 2] in (b'$', b'~')
416 ):
420 ):
417 # Skip '\', but only if it is escaping $ or ~
421 # Skip '\', but only if it is escaping $ or ~
418 res += path[index + 1 : index + 2]
422 res += path[index + 1 : index + 2]
419 index += 1
423 index += 1
420 else:
424 else:
421 res += c
425 res += c
422
426
423 index += 1
427 index += 1
424 return res
428 return res
425
429
426
430
427 # A sequence of backslashes is special iff it precedes a double quote:
431 # A sequence of backslashes is special iff it precedes a double quote:
428 # - if there's an even number of backslashes, the double quote is not
432 # - if there's an even number of backslashes, the double quote is not
429 # quoted (i.e. it ends the quoted region)
433 # quoted (i.e. it ends the quoted region)
430 # - if there's an odd number of backslashes, the double quote is quoted
434 # - if there's an odd number of backslashes, the double quote is quoted
431 # - in both cases, every pair of backslashes is unquoted into a single
435 # - in both cases, every pair of backslashes is unquoted into a single
432 # backslash
436 # backslash
433 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
437 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
434 # So, to quote a string, we must surround it in double quotes, double
438 # So, to quote a string, we must surround it in double quotes, double
435 # the number of backslashes that precede double quotes and add another
439 # the number of backslashes that precede double quotes and add another
436 # backslash before every double quote (being careful with the double
440 # backslash before every double quote (being careful with the double
437 # quote we've appended to the end)
441 # quote we've appended to the end)
438 _quotere = None
442 _quotere = None
439 _needsshellquote = None
443 _needsshellquote = None
440
444
441
445
442 def shellquote(s):
446 def shellquote(s):
443 r"""
447 r"""
444 >>> shellquote(br'C:\Users\xyz')
448 >>> shellquote(br'C:\Users\xyz')
445 '"C:\\Users\\xyz"'
449 '"C:\\Users\\xyz"'
446 >>> shellquote(br'C:\Users\xyz/mixed')
450 >>> shellquote(br'C:\Users\xyz/mixed')
447 '"C:\\Users\\xyz/mixed"'
451 '"C:\\Users\\xyz/mixed"'
448 >>> # Would be safe not to quote too, since it is all double backslashes
452 >>> # Would be safe not to quote too, since it is all double backslashes
449 >>> shellquote(br'C:\\Users\\xyz')
453 >>> shellquote(br'C:\\Users\\xyz')
450 '"C:\\\\Users\\\\xyz"'
454 '"C:\\\\Users\\\\xyz"'
451 >>> # But this must be quoted
455 >>> # But this must be quoted
452 >>> shellquote(br'C:\\Users\\xyz/abc')
456 >>> shellquote(br'C:\\Users\\xyz/abc')
453 '"C:\\\\Users\\\\xyz/abc"'
457 '"C:\\\\Users\\\\xyz/abc"'
454 """
458 """
455 global _quotere
459 global _quotere
456 if _quotere is None:
460 if _quotere is None:
457 _quotere = re.compile(br'(\\*)("|\\$)')
461 _quotere = re.compile(br'(\\*)("|\\$)')
458 global _needsshellquote
462 global _needsshellquote
459 if _needsshellquote is None:
463 if _needsshellquote is None:
460 # ":" is also treated as "safe character", because it is used as a part
464 # ":" is also treated as "safe character", because it is used as a part
461 # of path name on Windows. "\" is also part of a path name, but isn't
465 # of path name on Windows. "\" is also part of a path name, but isn't
462 # safe because shlex.split() (kind of) treats it as an escape char and
466 # safe because shlex.split() (kind of) treats it as an escape char and
463 # drops it. It will leave the next character, even if it is another
467 # drops it. It will leave the next character, even if it is another
464 # "\".
468 # "\".
465 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
469 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
466 if s and not _needsshellquote(s) and not _quotere.search(s):
470 if s and not _needsshellquote(s) and not _quotere.search(s):
467 # "s" shouldn't have to be quoted
471 # "s" shouldn't have to be quoted
468 return s
472 return s
469 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
473 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
470
474
471
475
472 def _unquote(s):
476 def _unquote(s):
473 if s.startswith(b'"') and s.endswith(b'"'):
477 if s.startswith(b'"') and s.endswith(b'"'):
474 return s[1:-1]
478 return s[1:-1]
475 return s
479 return s
476
480
477
481
478 def shellsplit(s):
482 def shellsplit(s):
479 """Parse a command string in cmd.exe way (best-effort)"""
483 """Parse a command string in cmd.exe way (best-effort)"""
480 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
484 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
481
485
482
486
483 # if you change this stub into a real check, please try to implement the
487 # if you change this stub into a real check, please try to implement the
484 # username and groupname functions above, too.
488 # username and groupname functions above, too.
485 def isowner(st):
489 def isowner(st):
486 return True
490 return True
487
491
488
492
489 def findexe(command):
493 def findexe(command):
490 '''Find executable for command searching like cmd.exe does.
494 '''Find executable for command searching like cmd.exe does.
491 If command is a basename then PATH is searched for command.
495 If command is a basename then PATH is searched for command.
492 PATH isn't searched if command is an absolute or relative path.
496 PATH isn't searched if command is an absolute or relative path.
493 An extension from PATHEXT is found and added if not present.
497 An extension from PATHEXT is found and added if not present.
494 If command isn't found None is returned.'''
498 If command isn't found None is returned.'''
495 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
499 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
496 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
500 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
497 if os.path.splitext(command)[1].lower() in pathexts:
501 if os.path.splitext(command)[1].lower() in pathexts:
498 pathexts = [b'']
502 pathexts = [b'']
499
503
500 def findexisting(pathcommand):
504 def findexisting(pathcommand):
501 """Will append extension (if needed) and return existing file"""
505 """Will append extension (if needed) and return existing file"""
502 for ext in pathexts:
506 for ext in pathexts:
503 executable = pathcommand + ext
507 executable = pathcommand + ext
504 if os.path.exists(executable):
508 if os.path.exists(executable):
505 return executable
509 return executable
506 return None
510 return None
507
511
508 if pycompat.ossep in command:
512 if pycompat.ossep in command:
509 return findexisting(command)
513 return findexisting(command)
510
514
511 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
515 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
512 executable = findexisting(os.path.join(path, command))
516 executable = findexisting(os.path.join(path, command))
513 if executable is not None:
517 if executable is not None:
514 return executable
518 return executable
515 return findexisting(os.path.expanduser(os.path.expandvars(command)))
519 return findexisting(os.path.expanduser(os.path.expandvars(command)))
516
520
517
521
518 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
522 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
519
523
520
524
521 def statfiles(files):
525 def statfiles(files):
522 '''Stat each file in files. Yield each stat, or None if a file
526 '''Stat each file in files. Yield each stat, or None if a file
523 does not exist or has a type we don't care about.
527 does not exist or has a type we don't care about.
524
528
525 Cluster and cache stat per directory to minimize number of OS stat calls.'''
529 Cluster and cache stat per directory to minimize number of OS stat calls.'''
526 dircache = {} # dirname -> filename -> status | None if file does not exist
530 dircache = {} # dirname -> filename -> status | None if file does not exist
527 getkind = stat.S_IFMT
531 getkind = stat.S_IFMT
528 for nf in files:
532 for nf in files:
529 nf = normcase(nf)
533 nf = normcase(nf)
530 dir, base = os.path.split(nf)
534 dir, base = os.path.split(nf)
531 if not dir:
535 if not dir:
532 dir = b'.'
536 dir = b'.'
533 cache = dircache.get(dir, None)
537 cache = dircache.get(dir, None)
534 if cache is None:
538 if cache is None:
535 try:
539 try:
536 dmap = {
540 dmap = {
537 normcase(n): s
541 normcase(n): s
538 for n, k, s in listdir(dir, True)
542 for n, k, s in listdir(dir, True)
539 if getkind(s.st_mode) in _wantedkinds
543 if getkind(s.st_mode) in _wantedkinds
540 }
544 }
541 except OSError as err:
545 except OSError as err:
542 # Python >= 2.5 returns ENOENT and adds winerror field
546 # Python >= 2.5 returns ENOENT and adds winerror field
543 # EINVAL is raised if dir is not a directory.
547 # EINVAL is raised if dir is not a directory.
544 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
548 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
545 raise
549 raise
546 dmap = {}
550 dmap = {}
547 cache = dircache.setdefault(dir, dmap)
551 cache = dircache.setdefault(dir, dmap)
548 yield cache.get(base, None)
552 yield cache.get(base, None)
549
553
550
554
551 def username(uid=None):
555 def username(uid=None):
552 """Return the name of the user with the given uid.
556 """Return the name of the user with the given uid.
553
557
554 If uid is None, return the name of the current user."""
558 If uid is None, return the name of the current user."""
555 if not uid:
559 if not uid:
556 return pycompat.fsencode(getpass.getuser())
560 return pycompat.fsencode(getpass.getuser())
557 return None
561 return None
558
562
559
563
560 def groupname(gid=None):
564 def groupname(gid=None):
561 """Return the name of the group with the given gid.
565 """Return the name of the group with the given gid.
562
566
563 If gid is None, return the name of the current group."""
567 If gid is None, return the name of the current group."""
564 return None
568 return None
565
569
566
570
567 def readlink(pathname):
571 def readlink(pathname):
568 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
572 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
569
573
570
574
571 def removedirs(name):
575 def removedirs(name):
572 """special version of os.removedirs that does not remove symlinked
576 """special version of os.removedirs that does not remove symlinked
573 directories or junction points if they actually contain files"""
577 directories or junction points if they actually contain files"""
574 if listdir(name):
578 if listdir(name):
575 return
579 return
576 os.rmdir(name)
580 os.rmdir(name)
577 head, tail = os.path.split(name)
581 head, tail = os.path.split(name)
578 if not tail:
582 if not tail:
579 head, tail = os.path.split(head)
583 head, tail = os.path.split(head)
580 while head and tail:
584 while head and tail:
581 try:
585 try:
582 if listdir(head):
586 if listdir(head):
583 return
587 return
584 os.rmdir(head)
588 os.rmdir(head)
585 except (ValueError, OSError):
589 except (ValueError, OSError):
586 break
590 break
587 head, tail = os.path.split(head)
591 head, tail = os.path.split(head)
588
592
589
593
590 def rename(src, dst):
594 def rename(src, dst):
591 '''atomically rename file src to dst, replacing dst if it exists'''
595 '''atomically rename file src to dst, replacing dst if it exists'''
592 try:
596 try:
593 os.rename(src, dst)
597 os.rename(src, dst)
594 except OSError as e:
598 except OSError as e:
595 if e.errno != errno.EEXIST:
599 if e.errno != errno.EEXIST:
596 raise
600 raise
597 unlink(dst)
601 unlink(dst)
598 os.rename(src, dst)
602 os.rename(src, dst)
599
603
600
604
601 def gethgcmd():
605 def gethgcmd():
602 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
606 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
603
607
604
608
605 def groupmembers(name):
609 def groupmembers(name):
606 # Don't support groups on Windows for now
610 # Don't support groups on Windows for now
607 raise KeyError
611 raise KeyError
608
612
609
613
610 def isexec(f):
614 def isexec(f):
611 return False
615 return False
612
616
613
617
614 class cachestat(object):
618 class cachestat(object):
615 def __init__(self, path):
619 def __init__(self, path):
616 pass
620 pass
617
621
618 def cacheable(self):
622 def cacheable(self):
619 return False
623 return False
620
624
621
625
622 def lookupreg(key, valname=None, scope=None):
626 def lookupreg(key, valname=None, scope=None):
623 ''' Look up a key/value name in the Windows registry.
627 ''' Look up a key/value name in the Windows registry.
624
628
625 valname: value name. If unspecified, the default value for the key
629 valname: value name. If unspecified, the default value for the key
626 is used.
630 is used.
627 scope: optionally specify scope for registry lookup, this can be
631 scope: optionally specify scope for registry lookup, this can be
628 a sequence of scopes to look up in order. Default (CURRENT_USER,
632 a sequence of scopes to look up in order. Default (CURRENT_USER,
629 LOCAL_MACHINE).
633 LOCAL_MACHINE).
630 '''
634 '''
631 if scope is None:
635 if scope is None:
632 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
636 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
633 elif not isinstance(scope, (list, tuple)):
637 elif not isinstance(scope, (list, tuple)):
634 scope = (scope,)
638 scope = (scope,)
635 for s in scope:
639 for s in scope:
636 try:
640 try:
637 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
641 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
638 name = valname and encoding.strfromlocal(valname) or valname
642 name = valname and encoding.strfromlocal(valname) or valname
639 val = winreg.QueryValueEx(hkey, name)[0]
643 val = winreg.QueryValueEx(hkey, name)[0]
640 # never let a Unicode string escape into the wild
644 # never let a Unicode string escape into the wild
641 return encoding.unitolocal(val)
645 return encoding.unitolocal(val)
642 except EnvironmentError:
646 except EnvironmentError:
643 pass
647 pass
644
648
645
649
646 expandglobs = True
650 expandglobs = True
647
651
648
652
649 def statislink(st):
653 def statislink(st):
650 '''check whether a stat result is a symlink'''
654 '''check whether a stat result is a symlink'''
651 return False
655 return False
652
656
653
657
654 def statisexec(st):
658 def statisexec(st):
655 '''check whether a stat result is an executable file'''
659 '''check whether a stat result is an executable file'''
656 return False
660 return False
657
661
658
662
659 def poll(fds):
663 def poll(fds):
660 # see posix.py for description
664 # see posix.py for description
661 raise NotImplementedError()
665 raise NotImplementedError()
662
666
663
667
664 def readpipe(pipe):
668 def readpipe(pipe):
665 """Read all available data from a pipe."""
669 """Read all available data from a pipe."""
666 chunks = []
670 chunks = []
667 while True:
671 while True:
668 size = win32.peekpipe(pipe)
672 size = win32.peekpipe(pipe)
669 if not size:
673 if not size:
670 break
674 break
671
675
672 s = pipe.read(size)
676 s = pipe.read(size)
673 if not s:
677 if not s:
674 break
678 break
675 chunks.append(s)
679 chunks.append(s)
676
680
677 return b''.join(chunks)
681 return b''.join(chunks)
678
682
679
683
680 def bindunixsocket(sock, path):
684 def bindunixsocket(sock, path):
681 raise NotImplementedError('unsupported platform')
685 raise NotImplementedError('unsupported platform')
@@ -1,302 +1,365 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
3 Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
4 """
4 """
5 from __future__ import absolute_import
5 from __future__ import absolute_import
6
6
7 import contextlib
7 import contextlib
8 import errno
8 import errno
9 import os
9 import os
10 import signal
10 import signal
11 import subprocess
11 import subprocess
12 import sys
12 import sys
13 import tempfile
13 import tempfile
14 import unittest
14 import unittest
15
15
16 from mercurial import pycompat
16 from mercurial import pycompat, util
17
17
18
18
19 if pycompat.ispy3:
19 if pycompat.ispy3:
20
20
21 def set_noninheritable(fd):
21 def set_noninheritable(fd):
22 # On Python 3, file descriptors are non-inheritable by default.
22 # On Python 3, file descriptors are non-inheritable by default.
23 pass
23 pass
24
24
25
25
26 else:
26 else:
27 if pycompat.iswindows:
27 if pycompat.iswindows:
28 # unused
28 # unused
29 set_noninheritable = None
29 set_noninheritable = None
30 else:
30 else:
31 import fcntl
31 import fcntl
32
32
33 def set_noninheritable(fd):
33 def set_noninheritable(fd):
34 old = fcntl.fcntl(fd, fcntl.F_GETFD)
34 old = fcntl.fcntl(fd, fcntl.F_GETFD)
35 fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
35 fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
36
36
37
37
38 TEST_BUFFERING_CHILD_SCRIPT = r'''
38 TEST_BUFFERING_CHILD_SCRIPT = r'''
39 import os
39 import os
40
40
41 from mercurial import dispatch
41 from mercurial import dispatch
42 from mercurial.utils import procutil
42 from mercurial.utils import procutil
43
43
44 dispatch.initstdio()
44 dispatch.initstdio()
45 procutil.{stream}.write(b'aaa')
45 procutil.{stream}.write(b'aaa')
46 os.write(procutil.{stream}.fileno(), b'[written aaa]')
46 os.write(procutil.{stream}.fileno(), b'[written aaa]')
47 procutil.{stream}.write(b'bbb\n')
47 procutil.{stream}.write(b'bbb\n')
48 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
48 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
49 '''
49 '''
50 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
50 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
51 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
51 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
52 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
52 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
53
53
54
54
55 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
55 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
56 import os
56 import os
57 import signal
57 import signal
58 import sys
58 import sys
59
59
60 from mercurial import dispatch
60 from mercurial import dispatch
61 from mercurial.utils import procutil
61 from mercurial.utils import procutil
62
62
63 signal.signal(signal.SIGINT, lambda *x: None)
63 signal.signal(signal.SIGINT, lambda *x: None)
64 dispatch.initstdio()
64 dispatch.initstdio()
65 write_result = procutil.{stream}.write(b'x' * 1048576)
65 write_result = procutil.{stream}.write(b'x' * 1048576)
66 with os.fdopen(
66 with os.fdopen(
67 os.open({write_result_fn!r}, os.O_WRONLY | getattr(os, 'O_TEMPORARY', 0)),
67 os.open({write_result_fn!r}, os.O_WRONLY | getattr(os, 'O_TEMPORARY', 0)),
68 'w',
68 'w',
69 ) as write_result_f:
69 ) as write_result_f:
70 write_result_f.write(str(write_result))
70 write_result_f.write(str(write_result))
71 '''
71 '''
72
72
73
73
74 TEST_BROKEN_PIPE_CHILD_SCRIPT = r'''
75 import os
76 import pickle
77
78 from mercurial import dispatch
79 from mercurial.utils import procutil
80
81 dispatch.initstdio()
82 procutil.stdin.read(1) # wait until parent process closed pipe
83 try:
84 procutil.{stream}.write(b'test')
85 procutil.{stream}.flush()
86 except EnvironmentError as e:
87 with os.fdopen(
88 os.open(
89 {err_fn!r},
90 os.O_WRONLY
91 | getattr(os, 'O_BINARY', 0)
92 | getattr(os, 'O_TEMPORARY', 0),
93 ),
94 'wb',
95 ) as err_f:
96 pickle.dump(e, err_f)
97 # Exit early to suppress further broken pipe errors at interpreter shutdown.
98 os._exit(0)
99 '''
100
101
74 @contextlib.contextmanager
102 @contextlib.contextmanager
75 def _closing(fds):
103 def _closing(fds):
76 try:
104 try:
77 yield
105 yield
78 finally:
106 finally:
79 for fd in fds:
107 for fd in fds:
80 try:
108 try:
81 os.close(fd)
109 os.close(fd)
82 except EnvironmentError:
110 except EnvironmentError:
83 pass
111 pass
84
112
85
113
86 # In the following, we set the FDs non-inheritable mainly to make it possible
114 # In the following, we set the FDs non-inheritable mainly to make it possible
87 # for tests to close the receiving end of the pipe / PTYs.
115 # for tests to close the receiving end of the pipe / PTYs.
88
116
89
117
90 @contextlib.contextmanager
118 @contextlib.contextmanager
91 def _devnull():
119 def _devnull():
92 devnull = os.open(os.devnull, os.O_WRONLY)
120 devnull = os.open(os.devnull, os.O_WRONLY)
93 # We don't have a receiving end, so it's not worth the effort on Python 2
121 # We don't have a receiving end, so it's not worth the effort on Python 2
94 # on Windows to make the FD non-inheritable.
122 # on Windows to make the FD non-inheritable.
95 with _closing([devnull]):
123 with _closing([devnull]):
96 yield (None, devnull)
124 yield (None, devnull)
97
125
98
126
99 @contextlib.contextmanager
127 @contextlib.contextmanager
100 def _pipes():
128 def _pipes():
101 rwpair = os.pipe()
129 rwpair = os.pipe()
102 # Pipes are already non-inheritable on Windows.
130 # Pipes are already non-inheritable on Windows.
103 if not pycompat.iswindows:
131 if not pycompat.iswindows:
104 set_noninheritable(rwpair[0])
132 set_noninheritable(rwpair[0])
105 set_noninheritable(rwpair[1])
133 set_noninheritable(rwpair[1])
106 with _closing(rwpair):
134 with _closing(rwpair):
107 yield rwpair
135 yield rwpair
108
136
109
137
110 @contextlib.contextmanager
138 @contextlib.contextmanager
111 def _ptys():
139 def _ptys():
112 if pycompat.iswindows:
140 if pycompat.iswindows:
113 raise unittest.SkipTest("PTYs are not supported on Windows")
141 raise unittest.SkipTest("PTYs are not supported on Windows")
114 import pty
142 import pty
115 import tty
143 import tty
116
144
117 rwpair = pty.openpty()
145 rwpair = pty.openpty()
118 set_noninheritable(rwpair[0])
146 set_noninheritable(rwpair[0])
119 set_noninheritable(rwpair[1])
147 set_noninheritable(rwpair[1])
120 with _closing(rwpair):
148 with _closing(rwpair):
121 tty.setraw(rwpair[0])
149 tty.setraw(rwpair[0])
122 yield rwpair
150 yield rwpair
123
151
124
152
125 def _readall(fd, buffer_size, initial_buf=None):
153 def _readall(fd, buffer_size, initial_buf=None):
126 buf = initial_buf or []
154 buf = initial_buf or []
127 while True:
155 while True:
128 try:
156 try:
129 s = os.read(fd, buffer_size)
157 s = os.read(fd, buffer_size)
130 except OSError as e:
158 except OSError as e:
131 if e.errno == errno.EIO:
159 if e.errno == errno.EIO:
132 # If the child-facing PTY got closed, reading from the
160 # If the child-facing PTY got closed, reading from the
133 # parent-facing PTY raises EIO.
161 # parent-facing PTY raises EIO.
134 break
162 break
135 raise
163 raise
136 if not s:
164 if not s:
137 break
165 break
138 buf.append(s)
166 buf.append(s)
139 return b''.join(buf)
167 return b''.join(buf)
140
168
141
169
142 class TestStdio(unittest.TestCase):
170 class TestStdio(unittest.TestCase):
143 def _test(
171 def _test(
144 self,
172 self,
145 child_script,
173 child_script,
146 stream,
174 stream,
147 rwpair_generator,
175 rwpair_generator,
148 check_output,
176 check_output,
149 python_args=[],
177 python_args=[],
150 post_child_check=None,
178 post_child_check=None,
179 stdin_generator=None,
151 ):
180 ):
152 assert stream in ('stdout', 'stderr')
181 assert stream in ('stdout', 'stderr')
153 with rwpair_generator() as (stream_receiver, child_stream), open(
182 if stdin_generator is None:
154 os.devnull, 'rb'
183 stdin_generator = open(os.devnull, 'rb')
155 ) as child_stdin:
184 with rwpair_generator() as (
185 stream_receiver,
186 child_stream,
187 ), stdin_generator as child_stdin:
156 proc = subprocess.Popen(
188 proc = subprocess.Popen(
157 [sys.executable] + python_args + ['-c', child_script],
189 [sys.executable] + python_args + ['-c', child_script],
158 stdin=child_stdin,
190 stdin=child_stdin,
159 stdout=child_stream if stream == 'stdout' else None,
191 stdout=child_stream if stream == 'stdout' else None,
160 stderr=child_stream if stream == 'stderr' else None,
192 stderr=child_stream if stream == 'stderr' else None,
161 )
193 )
162 try:
194 try:
163 os.close(child_stream)
195 os.close(child_stream)
164 if stream_receiver is not None:
196 if stream_receiver is not None:
165 check_output(stream_receiver, proc)
197 check_output(stream_receiver, proc)
166 except: # re-raises
198 except: # re-raises
167 proc.terminate()
199 proc.terminate()
168 raise
200 raise
169 finally:
201 finally:
170 retcode = proc.wait()
202 retcode = proc.wait()
171 self.assertEqual(retcode, 0)
203 self.assertEqual(retcode, 0)
172 if post_child_check is not None:
204 if post_child_check is not None:
173 post_child_check()
205 post_child_check()
174
206
175 def _test_buffering(
207 def _test_buffering(
176 self, stream, rwpair_generator, expected_output, python_args=[]
208 self, stream, rwpair_generator, expected_output, python_args=[]
177 ):
209 ):
178 def check_output(stream_receiver, proc):
210 def check_output(stream_receiver, proc):
179 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
211 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
180
212
181 self._test(
213 self._test(
182 TEST_BUFFERING_CHILD_SCRIPT.format(stream=stream),
214 TEST_BUFFERING_CHILD_SCRIPT.format(stream=stream),
183 stream,
215 stream,
184 rwpair_generator,
216 rwpair_generator,
185 check_output,
217 check_output,
186 python_args,
218 python_args,
187 )
219 )
188
220
189 def test_buffering_stdout_devnull(self):
221 def test_buffering_stdout_devnull(self):
190 self._test_buffering('stdout', _devnull, None)
222 self._test_buffering('stdout', _devnull, None)
191
223
192 def test_buffering_stdout_pipes(self):
224 def test_buffering_stdout_pipes(self):
193 self._test_buffering('stdout', _pipes, FULLY_BUFFERED)
225 self._test_buffering('stdout', _pipes, FULLY_BUFFERED)
194
226
195 def test_buffering_stdout_ptys(self):
227 def test_buffering_stdout_ptys(self):
196 self._test_buffering('stdout', _ptys, LINE_BUFFERED)
228 self._test_buffering('stdout', _ptys, LINE_BUFFERED)
197
229
198 def test_buffering_stdout_devnull_unbuffered(self):
230 def test_buffering_stdout_devnull_unbuffered(self):
199 self._test_buffering('stdout', _devnull, None, python_args=['-u'])
231 self._test_buffering('stdout', _devnull, None, python_args=['-u'])
200
232
201 def test_buffering_stdout_pipes_unbuffered(self):
233 def test_buffering_stdout_pipes_unbuffered(self):
202 self._test_buffering('stdout', _pipes, UNBUFFERED, python_args=['-u'])
234 self._test_buffering('stdout', _pipes, UNBUFFERED, python_args=['-u'])
203
235
204 def test_buffering_stdout_ptys_unbuffered(self):
236 def test_buffering_stdout_ptys_unbuffered(self):
205 self._test_buffering('stdout', _ptys, UNBUFFERED, python_args=['-u'])
237 self._test_buffering('stdout', _ptys, UNBUFFERED, python_args=['-u'])
206
238
207 if not pycompat.ispy3 and not pycompat.iswindows:
239 if not pycompat.ispy3 and not pycompat.iswindows:
208 # On Python 2 on non-Windows, we manually open stdout in line-buffered
240 # On Python 2 on non-Windows, we manually open stdout in line-buffered
209 # mode if connected to a TTY. We should check if Python was configured
241 # mode if connected to a TTY. We should check if Python was configured
210 # to use unbuffered stdout, but it's hard to do that.
242 # to use unbuffered stdout, but it's hard to do that.
211 test_buffering_stdout_ptys_unbuffered = unittest.expectedFailure(
243 test_buffering_stdout_ptys_unbuffered = unittest.expectedFailure(
212 test_buffering_stdout_ptys_unbuffered
244 test_buffering_stdout_ptys_unbuffered
213 )
245 )
214
246
215 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
247 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
216 if not pycompat.ispy3 and pycompat.isdarwin:
248 if not pycompat.ispy3 and pycompat.isdarwin:
217 # Python 2 doesn't always retry on EINTR, but the libc might retry.
249 # Python 2 doesn't always retry on EINTR, but the libc might retry.
218 # So far, it was observed only on macOS that EINTR is raised at the
250 # So far, it was observed only on macOS that EINTR is raised at the
219 # Python level. As Python 2 support will be dropped soon-ish, we
251 # Python level. As Python 2 support will be dropped soon-ish, we
220 # won't attempt to fix it.
252 # won't attempt to fix it.
221 raise unittest.SkipTest("raises EINTR on macOS")
253 raise unittest.SkipTest("raises EINTR on macOS")
222
254
223 def check_output(stream_receiver, proc):
255 def check_output(stream_receiver, proc):
224 if not pycompat.iswindows:
256 if not pycompat.iswindows:
225 # On Unix, we can provoke a partial write() by interrupting it
257 # On Unix, we can provoke a partial write() by interrupting it
226 # by a signal handler as soon as a bit of data was written.
258 # by a signal handler as soon as a bit of data was written.
227 # We test that write() is called until all data is written.
259 # We test that write() is called until all data is written.
228 buf = [os.read(stream_receiver, 1)]
260 buf = [os.read(stream_receiver, 1)]
229 proc.send_signal(signal.SIGINT)
261 proc.send_signal(signal.SIGINT)
230 else:
262 else:
231 # On Windows, there doesn't seem to be a way to cause partial
263 # On Windows, there doesn't seem to be a way to cause partial
232 # writes.
264 # writes.
233 buf = []
265 buf = []
234 self.assertEqual(
266 self.assertEqual(
235 _readall(stream_receiver, 131072, buf), b'x' * 1048576
267 _readall(stream_receiver, 131072, buf), b'x' * 1048576
236 )
268 )
237
269
238 def post_child_check():
270 def post_child_check():
239 write_result_str = write_result_f.read()
271 write_result_str = write_result_f.read()
240 if pycompat.ispy3:
272 if pycompat.ispy3:
241 # On Python 3, we test that the correct number of bytes is
273 # On Python 3, we test that the correct number of bytes is
242 # claimed to have been written.
274 # claimed to have been written.
243 expected_write_result_str = '1048576'
275 expected_write_result_str = '1048576'
244 else:
276 else:
245 # On Python 2, we only check that the large write does not
277 # On Python 2, we only check that the large write does not
246 # crash.
278 # crash.
247 expected_write_result_str = 'None'
279 expected_write_result_str = 'None'
248 self.assertEqual(write_result_str, expected_write_result_str)
280 self.assertEqual(write_result_str, expected_write_result_str)
249
281
250 with tempfile.NamedTemporaryFile('r') as write_result_f:
282 with tempfile.NamedTemporaryFile('r') as write_result_f:
251 self._test(
283 self._test(
252 TEST_LARGE_WRITE_CHILD_SCRIPT.format(
284 TEST_LARGE_WRITE_CHILD_SCRIPT.format(
253 stream=stream, write_result_fn=write_result_f.name
285 stream=stream, write_result_fn=write_result_f.name
254 ),
286 ),
255 stream,
287 stream,
256 rwpair_generator,
288 rwpair_generator,
257 check_output,
289 check_output,
258 python_args,
290 python_args,
259 post_child_check=post_child_check,
291 post_child_check=post_child_check,
260 )
292 )
261
293
262 def test_large_write_stdout_devnull(self):
294 def test_large_write_stdout_devnull(self):
263 self._test_large_write('stdout', _devnull)
295 self._test_large_write('stdout', _devnull)
264
296
265 def test_large_write_stdout_pipes(self):
297 def test_large_write_stdout_pipes(self):
266 self._test_large_write('stdout', _pipes)
298 self._test_large_write('stdout', _pipes)
267
299
268 def test_large_write_stdout_ptys(self):
300 def test_large_write_stdout_ptys(self):
269 self._test_large_write('stdout', _ptys)
301 self._test_large_write('stdout', _ptys)
270
302
271 def test_large_write_stdout_devnull_unbuffered(self):
303 def test_large_write_stdout_devnull_unbuffered(self):
272 self._test_large_write('stdout', _devnull, python_args=['-u'])
304 self._test_large_write('stdout', _devnull, python_args=['-u'])
273
305
274 def test_large_write_stdout_pipes_unbuffered(self):
306 def test_large_write_stdout_pipes_unbuffered(self):
275 self._test_large_write('stdout', _pipes, python_args=['-u'])
307 self._test_large_write('stdout', _pipes, python_args=['-u'])
276
308
277 def test_large_write_stdout_ptys_unbuffered(self):
309 def test_large_write_stdout_ptys_unbuffered(self):
278 self._test_large_write('stdout', _ptys, python_args=['-u'])
310 self._test_large_write('stdout', _ptys, python_args=['-u'])
279
311
280 def test_large_write_stderr_devnull(self):
312 def test_large_write_stderr_devnull(self):
281 self._test_large_write('stderr', _devnull)
313 self._test_large_write('stderr', _devnull)
282
314
283 def test_large_write_stderr_pipes(self):
315 def test_large_write_stderr_pipes(self):
284 self._test_large_write('stderr', _pipes)
316 self._test_large_write('stderr', _pipes)
285
317
286 def test_large_write_stderr_ptys(self):
318 def test_large_write_stderr_ptys(self):
287 self._test_large_write('stderr', _ptys)
319 self._test_large_write('stderr', _ptys)
288
320
289 def test_large_write_stderr_devnull_unbuffered(self):
321 def test_large_write_stderr_devnull_unbuffered(self):
290 self._test_large_write('stderr', _devnull, python_args=['-u'])
322 self._test_large_write('stderr', _devnull, python_args=['-u'])
291
323
292 def test_large_write_stderr_pipes_unbuffered(self):
324 def test_large_write_stderr_pipes_unbuffered(self):
293 self._test_large_write('stderr', _pipes, python_args=['-u'])
325 self._test_large_write('stderr', _pipes, python_args=['-u'])
294
326
295 def test_large_write_stderr_ptys_unbuffered(self):
327 def test_large_write_stderr_ptys_unbuffered(self):
296 self._test_large_write('stderr', _ptys, python_args=['-u'])
328 self._test_large_write('stderr', _ptys, python_args=['-u'])
297
329
330 def _test_broken_pipe(self, stream):
331 assert stream in ('stdout', 'stderr')
332
333 def check_output(stream_receiver, proc):
334 os.close(stream_receiver)
335 proc.stdin.write(b'x')
336 proc.stdin.close()
337
338 def post_child_check():
339 err = util.pickle.load(err_f)
340 self.assertEqual(err.errno, errno.EPIPE)
341 self.assertEqual(err.strerror, "Broken pipe")
342
343 with tempfile.NamedTemporaryFile('rb') as err_f:
344 self._test(
345 TEST_BROKEN_PIPE_CHILD_SCRIPT.format(
346 stream=stream, err_fn=err_f.name
347 ),
348 stream,
349 _pipes,
350 check_output,
351 post_child_check=post_child_check,
352 stdin_generator=util.nullcontextmanager(subprocess.PIPE),
353 )
354
355 def test_broken_pipe_stdout(self):
356 self._test_broken_pipe('stdout')
357
358 def test_broken_pipe_stderr(self):
359 self._test_broken_pipe('stderr')
360
298
361
299 if __name__ == '__main__':
362 if __name__ == '__main__':
300 import silenttestrunner
363 import silenttestrunner
301
364
302 silenttestrunner.main(__name__)
365 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now