##// END OF EJS Templates
procutil: make stream detection in make_line_buffered more correct and strict...
Manuel Jacob -
r50273:094a5fa3 6.2 stable
parent child Browse files
Show More
@@ -1,770 +1,790
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 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@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
10
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import io
13 import io
14 import os
14 import os
15 import signal
15 import signal
16 import subprocess
16 import subprocess
17 import sys
17 import sys
18 import threading
18 import threading
19 import time
19 import time
20
20
21 from ..i18n import _
21 from ..i18n import _
22 from ..pycompat import (
22 from ..pycompat import (
23 getattr,
23 getattr,
24 open,
24 open,
25 )
25 )
26
26
27 from .. import (
27 from .. import (
28 encoding,
28 encoding,
29 error,
29 error,
30 policy,
30 policy,
31 pycompat,
31 pycompat,
32 )
32 )
33
33
34 # Import like this to keep import-checker happy
34 # Import like this to keep import-checker happy
35 from ..utils import resourceutil
35 from ..utils import resourceutil
36
36
37 osutil = policy.importmod('osutil')
37 osutil = policy.importmod('osutil')
38
38
39 if pycompat.iswindows:
39 if pycompat.iswindows:
40 from .. import windows as platform
40 from .. import windows as platform
41 else:
41 else:
42 from .. import posix as platform
42 from .. import posix as platform
43
43
44
44
45 def isatty(fp):
45 def isatty(fp):
46 try:
46 try:
47 return fp.isatty()
47 return fp.isatty()
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50
50
51
51
52 class BadFile(io.RawIOBase):
52 class BadFile(io.RawIOBase):
53 """Dummy file object to simulate closed stdio behavior"""
53 """Dummy file object to simulate closed stdio behavior"""
54
54
55 def readinto(self, b):
55 def readinto(self, b):
56 raise IOError(errno.EBADF, 'Bad file descriptor')
56 raise IOError(errno.EBADF, 'Bad file descriptor')
57
57
58 def write(self, b):
58 def write(self, b):
59 raise IOError(errno.EBADF, 'Bad file descriptor')
59 raise IOError(errno.EBADF, 'Bad file descriptor')
60
60
61
61
62 class LineBufferedWrapper:
62 class LineBufferedWrapper:
63 def __init__(self, orig):
63 def __init__(self, orig):
64 self.orig = orig
64 self.orig = orig
65
65
66 def __getattr__(self, attr):
66 def __getattr__(self, attr):
67 return getattr(self.orig, attr)
67 return getattr(self.orig, attr)
68
68
69 def write(self, s):
69 def write(self, s):
70 orig = self.orig
70 orig = self.orig
71 res = orig.write(s)
71 res = orig.write(s)
72 if s.endswith(b'\n'):
72 if s.endswith(b'\n'):
73 orig.flush()
73 orig.flush()
74 return res
74 return res
75
75
76
76
77 # pytype: disable=attribute-error
77 # pytype: disable=attribute-error
78 io.BufferedIOBase.register(LineBufferedWrapper)
78 io.BufferedIOBase.register(LineBufferedWrapper)
79 # pytype: enable=attribute-error
79 # pytype: enable=attribute-error
80
80
81
81
82 def make_line_buffered(stream):
82 def make_line_buffered(stream):
83 if not isinstance(stream, io.BufferedIOBase):
83 # First, check if we need to wrap the stream.
84 # On Python 3, buffered streams can be expected to subclass
84 check_stream = stream
85 # BufferedIOBase. This is definitively the case for the streams
85 while True:
86 # initialized by the interpreter. For unbuffered streams, we don't need
86 if isinstance(check_stream, WriteAllWrapper):
87 # to emulate line buffering.
87 check_stream = check_stream.orig
88 elif pycompat.iswindows and isinstance(
89 check_stream,
90 # pytype: disable=module-attr
91 platform.winstdout
92 # pytype: enable=module-attr
93 ):
94 check_stream = check_stream.fp
95 else:
96 break
97 if isinstance(check_stream, io.RawIOBase):
98 # The stream is unbuffered, we don't need to emulate line buffering.
88 return stream
99 return stream
100 elif isinstance(check_stream, io.BufferedIOBase):
101 # The stream supports some kind of buffering. We can't assume that
102 # lines are flushed. Fall back to wrapping the stream.
103 pass
104 else:
105 raise NotImplementedError(
106 "can't determine whether stream is buffered or not"
107 )
108
89 if isinstance(stream, LineBufferedWrapper):
109 if isinstance(stream, LineBufferedWrapper):
90 return stream
110 return stream
91 return LineBufferedWrapper(stream)
111 return LineBufferedWrapper(stream)
92
112
93
113
94 def unwrap_line_buffered(stream):
114 def unwrap_line_buffered(stream):
95 if isinstance(stream, LineBufferedWrapper):
115 if isinstance(stream, LineBufferedWrapper):
96 assert not isinstance(stream.orig, LineBufferedWrapper)
116 assert not isinstance(stream.orig, LineBufferedWrapper)
97 return stream.orig
117 return stream.orig
98 return stream
118 return stream
99
119
100
120
101 class WriteAllWrapper:
121 class WriteAllWrapper:
102 def __init__(self, orig):
122 def __init__(self, orig):
103 self.orig = orig
123 self.orig = orig
104
124
105 def __getattr__(self, attr):
125 def __getattr__(self, attr):
106 return getattr(self.orig, attr)
126 return getattr(self.orig, attr)
107
127
108 def write(self, s):
128 def write(self, s):
109 write1 = self.orig.write
129 write1 = self.orig.write
110 m = memoryview(s)
130 m = memoryview(s)
111 total_to_write = len(s)
131 total_to_write = len(s)
112 total_written = 0
132 total_written = 0
113 while total_written < total_to_write:
133 while total_written < total_to_write:
114 c = write1(m[total_written:])
134 c = write1(m[total_written:])
115 if c:
135 if c:
116 total_written += c
136 total_written += c
117 return total_written
137 return total_written
118
138
119
139
120 # pytype: disable=attribute-error
140 # pytype: disable=attribute-error
121 io.IOBase.register(WriteAllWrapper)
141 io.IOBase.register(WriteAllWrapper)
122 # pytype: enable=attribute-error
142 # pytype: enable=attribute-error
123
143
124
144
125 def _make_write_all(stream):
145 def _make_write_all(stream):
126 if isinstance(stream, WriteAllWrapper):
146 if isinstance(stream, WriteAllWrapper):
127 return stream
147 return stream
128 if isinstance(stream, io.BufferedIOBase):
148 if isinstance(stream, io.BufferedIOBase):
129 # The io.BufferedIOBase.write() contract guarantees that all data is
149 # The io.BufferedIOBase.write() contract guarantees that all data is
130 # written.
150 # written.
131 return stream
151 return stream
132 # In general, the write() method of streams is free to write only part of
152 # In general, the write() method of streams is free to write only part of
133 # the data.
153 # the data.
134 return WriteAllWrapper(stream)
154 return WriteAllWrapper(stream)
135
155
136
156
137 # Python 3 implements its own I/O streams. Unlike stdio of C library,
157 # Python 3 implements its own I/O streams. Unlike stdio of C library,
138 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
158 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
139
159
140 # TODO: .buffer might not exist if std streams were replaced; we'll need
160 # TODO: .buffer might not exist if std streams were replaced; we'll need
141 # a silly wrapper to make a bytes stream backed by a unicode one.
161 # a silly wrapper to make a bytes stream backed by a unicode one.
142
162
143 if sys.stdin is None:
163 if sys.stdin is None:
144 stdin = BadFile()
164 stdin = BadFile()
145 else:
165 else:
146 stdin = sys.stdin.buffer
166 stdin = sys.stdin.buffer
147 if sys.stdout is None:
167 if sys.stdout is None:
148 stdout = BadFile()
168 stdout = BadFile()
149 else:
169 else:
150 stdout = _make_write_all(sys.stdout.buffer)
170 stdout = _make_write_all(sys.stdout.buffer)
151 if sys.stderr is None:
171 if sys.stderr is None:
152 stderr = BadFile()
172 stderr = BadFile()
153 else:
173 else:
154 stderr = _make_write_all(sys.stderr.buffer)
174 stderr = _make_write_all(sys.stderr.buffer)
155
175
156 if pycompat.iswindows:
176 if pycompat.iswindows:
157 # Work around Windows bugs.
177 # Work around Windows bugs.
158 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
178 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
159 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
179 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
160 if isatty(stdout):
180 if isatty(stdout):
161 # The standard library doesn't offer line-buffered binary streams.
181 # The standard library doesn't offer line-buffered binary streams.
162 stdout = make_line_buffered(stdout)
182 stdout = make_line_buffered(stdout)
163
183
164 findexe = platform.findexe
184 findexe = platform.findexe
165 _gethgcmd = platform.gethgcmd
185 _gethgcmd = platform.gethgcmd
166 getuser = platform.getuser
186 getuser = platform.getuser
167 getpid = os.getpid
187 getpid = os.getpid
168 hidewindow = platform.hidewindow
188 hidewindow = platform.hidewindow
169 readpipe = platform.readpipe
189 readpipe = platform.readpipe
170 setbinary = platform.setbinary
190 setbinary = platform.setbinary
171 setsignalhandler = platform.setsignalhandler
191 setsignalhandler = platform.setsignalhandler
172 shellquote = platform.shellquote
192 shellquote = platform.shellquote
173 shellsplit = platform.shellsplit
193 shellsplit = platform.shellsplit
174 spawndetached = platform.spawndetached
194 spawndetached = platform.spawndetached
175 sshargs = platform.sshargs
195 sshargs = platform.sshargs
176 testpid = platform.testpid
196 testpid = platform.testpid
177
197
178 try:
198 try:
179 setprocname = osutil.setprocname
199 setprocname = osutil.setprocname
180 except AttributeError:
200 except AttributeError:
181 pass
201 pass
182 try:
202 try:
183 unblocksignal = osutil.unblocksignal
203 unblocksignal = osutil.unblocksignal
184 except AttributeError:
204 except AttributeError:
185 pass
205 pass
186
206
187 closefds = pycompat.isposix
207 closefds = pycompat.isposix
188
208
189
209
190 def explainexit(code):
210 def explainexit(code):
191 """return a message describing a subprocess status
211 """return a message describing a subprocess status
192 (codes from kill are negative - not os.system/wait encoding)"""
212 (codes from kill are negative - not os.system/wait encoding)"""
193 if code >= 0:
213 if code >= 0:
194 return _(b"exited with status %d") % code
214 return _(b"exited with status %d") % code
195 return _(b"killed by signal %d") % -code
215 return _(b"killed by signal %d") % -code
196
216
197
217
198 class _pfile:
218 class _pfile:
199 """File-like wrapper for a stream opened by subprocess.Popen()"""
219 """File-like wrapper for a stream opened by subprocess.Popen()"""
200
220
201 def __init__(self, proc, fp):
221 def __init__(self, proc, fp):
202 self._proc = proc
222 self._proc = proc
203 self._fp = fp
223 self._fp = fp
204
224
205 def close(self):
225 def close(self):
206 # unlike os.popen(), this returns an integer in subprocess coding
226 # unlike os.popen(), this returns an integer in subprocess coding
207 self._fp.close()
227 self._fp.close()
208 return self._proc.wait()
228 return self._proc.wait()
209
229
210 def __iter__(self):
230 def __iter__(self):
211 return iter(self._fp)
231 return iter(self._fp)
212
232
213 def __getattr__(self, attr):
233 def __getattr__(self, attr):
214 return getattr(self._fp, attr)
234 return getattr(self._fp, attr)
215
235
216 def __enter__(self):
236 def __enter__(self):
217 return self
237 return self
218
238
219 def __exit__(self, exc_type, exc_value, exc_tb):
239 def __exit__(self, exc_type, exc_value, exc_tb):
220 self.close()
240 self.close()
221
241
222
242
223 def popen(cmd, mode=b'rb', bufsize=-1):
243 def popen(cmd, mode=b'rb', bufsize=-1):
224 if mode == b'rb':
244 if mode == b'rb':
225 return _popenreader(cmd, bufsize)
245 return _popenreader(cmd, bufsize)
226 elif mode == b'wb':
246 elif mode == b'wb':
227 return _popenwriter(cmd, bufsize)
247 return _popenwriter(cmd, bufsize)
228 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
248 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
229
249
230
250
231 def _popenreader(cmd, bufsize):
251 def _popenreader(cmd, bufsize):
232 p = subprocess.Popen(
252 p = subprocess.Popen(
233 tonativestr(cmd),
253 tonativestr(cmd),
234 shell=True,
254 shell=True,
235 bufsize=bufsize,
255 bufsize=bufsize,
236 close_fds=closefds,
256 close_fds=closefds,
237 stdout=subprocess.PIPE,
257 stdout=subprocess.PIPE,
238 )
258 )
239 return _pfile(p, p.stdout)
259 return _pfile(p, p.stdout)
240
260
241
261
242 def _popenwriter(cmd, bufsize):
262 def _popenwriter(cmd, bufsize):
243 p = subprocess.Popen(
263 p = subprocess.Popen(
244 tonativestr(cmd),
264 tonativestr(cmd),
245 shell=True,
265 shell=True,
246 bufsize=bufsize,
266 bufsize=bufsize,
247 close_fds=closefds,
267 close_fds=closefds,
248 stdin=subprocess.PIPE,
268 stdin=subprocess.PIPE,
249 )
269 )
250 return _pfile(p, p.stdin)
270 return _pfile(p, p.stdin)
251
271
252
272
253 def popen2(cmd, env=None):
273 def popen2(cmd, env=None):
254 # Setting bufsize to -1 lets the system decide the buffer size.
274 # Setting bufsize to -1 lets the system decide the buffer size.
255 # The default for bufsize is 0, meaning unbuffered. This leads to
275 # The default for bufsize is 0, meaning unbuffered. This leads to
256 # poor performance on Mac OS X: http://bugs.python.org/issue4194
276 # poor performance on Mac OS X: http://bugs.python.org/issue4194
257 p = subprocess.Popen(
277 p = subprocess.Popen(
258 tonativestr(cmd),
278 tonativestr(cmd),
259 shell=True,
279 shell=True,
260 bufsize=-1,
280 bufsize=-1,
261 close_fds=closefds,
281 close_fds=closefds,
262 stdin=subprocess.PIPE,
282 stdin=subprocess.PIPE,
263 stdout=subprocess.PIPE,
283 stdout=subprocess.PIPE,
264 env=tonativeenv(env),
284 env=tonativeenv(env),
265 )
285 )
266 return p.stdin, p.stdout
286 return p.stdin, p.stdout
267
287
268
288
269 def popen3(cmd, env=None):
289 def popen3(cmd, env=None):
270 stdin, stdout, stderr, p = popen4(cmd, env)
290 stdin, stdout, stderr, p = popen4(cmd, env)
271 return stdin, stdout, stderr
291 return stdin, stdout, stderr
272
292
273
293
274 def popen4(cmd, env=None, bufsize=-1):
294 def popen4(cmd, env=None, bufsize=-1):
275 p = subprocess.Popen(
295 p = subprocess.Popen(
276 tonativestr(cmd),
296 tonativestr(cmd),
277 shell=True,
297 shell=True,
278 bufsize=bufsize,
298 bufsize=bufsize,
279 close_fds=closefds,
299 close_fds=closefds,
280 stdin=subprocess.PIPE,
300 stdin=subprocess.PIPE,
281 stdout=subprocess.PIPE,
301 stdout=subprocess.PIPE,
282 stderr=subprocess.PIPE,
302 stderr=subprocess.PIPE,
283 env=tonativeenv(env),
303 env=tonativeenv(env),
284 )
304 )
285 return p.stdin, p.stdout, p.stderr, p
305 return p.stdin, p.stdout, p.stderr, p
286
306
287
307
288 def pipefilter(s, cmd):
308 def pipefilter(s, cmd):
289 '''filter string S through command CMD, returning its output'''
309 '''filter string S through command CMD, returning its output'''
290 p = subprocess.Popen(
310 p = subprocess.Popen(
291 tonativestr(cmd),
311 tonativestr(cmd),
292 shell=True,
312 shell=True,
293 close_fds=closefds,
313 close_fds=closefds,
294 stdin=subprocess.PIPE,
314 stdin=subprocess.PIPE,
295 stdout=subprocess.PIPE,
315 stdout=subprocess.PIPE,
296 )
316 )
297 pout, perr = p.communicate(s)
317 pout, perr = p.communicate(s)
298 return pout
318 return pout
299
319
300
320
301 def tempfilter(s, cmd):
321 def tempfilter(s, cmd):
302 """filter string S through a pair of temporary files with CMD.
322 """filter string S through a pair of temporary files with CMD.
303 CMD is used as a template to create the real command to be run,
323 CMD is used as a template to create the real command to be run,
304 with the strings INFILE and OUTFILE replaced by the real names of
324 with the strings INFILE and OUTFILE replaced by the real names of
305 the temporary files generated."""
325 the temporary files generated."""
306 inname, outname = None, None
326 inname, outname = None, None
307 try:
327 try:
308 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
328 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
309 fp = os.fdopen(infd, 'wb')
329 fp = os.fdopen(infd, 'wb')
310 fp.write(s)
330 fp.write(s)
311 fp.close()
331 fp.close()
312 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
332 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
313 os.close(outfd)
333 os.close(outfd)
314 cmd = cmd.replace(b'INFILE', inname)
334 cmd = cmd.replace(b'INFILE', inname)
315 cmd = cmd.replace(b'OUTFILE', outname)
335 cmd = cmd.replace(b'OUTFILE', outname)
316 code = system(cmd)
336 code = system(cmd)
317 if pycompat.sysplatform == b'OpenVMS' and code & 1:
337 if pycompat.sysplatform == b'OpenVMS' and code & 1:
318 code = 0
338 code = 0
319 if code:
339 if code:
320 raise error.Abort(
340 raise error.Abort(
321 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
341 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
322 )
342 )
323 with open(outname, b'rb') as fp:
343 with open(outname, b'rb') as fp:
324 return fp.read()
344 return fp.read()
325 finally:
345 finally:
326 try:
346 try:
327 if inname:
347 if inname:
328 os.unlink(inname)
348 os.unlink(inname)
329 except OSError:
349 except OSError:
330 pass
350 pass
331 try:
351 try:
332 if outname:
352 if outname:
333 os.unlink(outname)
353 os.unlink(outname)
334 except OSError:
354 except OSError:
335 pass
355 pass
336
356
337
357
338 _filtertable = {
358 _filtertable = {
339 b'tempfile:': tempfilter,
359 b'tempfile:': tempfilter,
340 b'pipe:': pipefilter,
360 b'pipe:': pipefilter,
341 }
361 }
342
362
343
363
344 def filter(s, cmd):
364 def filter(s, cmd):
345 """filter a string through a command that transforms its input to its
365 """filter a string through a command that transforms its input to its
346 output"""
366 output"""
347 for name, fn in _filtertable.items():
367 for name, fn in _filtertable.items():
348 if cmd.startswith(name):
368 if cmd.startswith(name):
349 return fn(s, cmd[len(name) :].lstrip())
369 return fn(s, cmd[len(name) :].lstrip())
350 return pipefilter(s, cmd)
370 return pipefilter(s, cmd)
351
371
352
372
353 _hgexecutable = None
373 _hgexecutable = None
354
374
355
375
356 def hgexecutable():
376 def hgexecutable():
357 """return location of the 'hg' executable.
377 """return location of the 'hg' executable.
358
378
359 Defaults to $HG or 'hg' in the search path.
379 Defaults to $HG or 'hg' in the search path.
360 """
380 """
361 if _hgexecutable is None:
381 if _hgexecutable is None:
362 hg = encoding.environ.get(b'HG')
382 hg = encoding.environ.get(b'HG')
363 mainmod = sys.modules['__main__']
383 mainmod = sys.modules['__main__']
364 if hg:
384 if hg:
365 _sethgexecutable(hg)
385 _sethgexecutable(hg)
366 elif resourceutil.mainfrozen():
386 elif resourceutil.mainfrozen():
367 if getattr(sys, 'frozen', None) == 'macosx_app':
387 if getattr(sys, 'frozen', None) == 'macosx_app':
368 # Env variable set by py2app
388 # Env variable set by py2app
369 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
389 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
370 else:
390 else:
371 _sethgexecutable(pycompat.sysexecutable)
391 _sethgexecutable(pycompat.sysexecutable)
372 elif (
392 elif (
373 not pycompat.iswindows
393 not pycompat.iswindows
374 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
394 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
375 ):
395 ):
376 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
396 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
377 else:
397 else:
378 _sethgexecutable(
398 _sethgexecutable(
379 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
399 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
380 )
400 )
381 return _hgexecutable
401 return _hgexecutable
382
402
383
403
384 def _sethgexecutable(path):
404 def _sethgexecutable(path):
385 """set location of the 'hg' executable"""
405 """set location of the 'hg' executable"""
386 global _hgexecutable
406 global _hgexecutable
387 _hgexecutable = path
407 _hgexecutable = path
388
408
389
409
390 def _testfileno(f, stdf):
410 def _testfileno(f, stdf):
391 fileno = getattr(f, 'fileno', None)
411 fileno = getattr(f, 'fileno', None)
392 try:
412 try:
393 return fileno and fileno() == stdf.fileno()
413 return fileno and fileno() == stdf.fileno()
394 except io.UnsupportedOperation:
414 except io.UnsupportedOperation:
395 return False # fileno() raised UnsupportedOperation
415 return False # fileno() raised UnsupportedOperation
396
416
397
417
398 def isstdin(f):
418 def isstdin(f):
399 return _testfileno(f, sys.__stdin__)
419 return _testfileno(f, sys.__stdin__)
400
420
401
421
402 def isstdout(f):
422 def isstdout(f):
403 return _testfileno(f, sys.__stdout__)
423 return _testfileno(f, sys.__stdout__)
404
424
405
425
406 def protectstdio(uin, uout):
426 def protectstdio(uin, uout):
407 """Duplicate streams and redirect original if (uin, uout) are stdio
427 """Duplicate streams and redirect original if (uin, uout) are stdio
408
428
409 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
429 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
410 redirected to stderr so the output is still readable.
430 redirected to stderr so the output is still readable.
411
431
412 Returns (fin, fout) which point to the original (uin, uout) fds, but
432 Returns (fin, fout) which point to the original (uin, uout) fds, but
413 may be copy of (uin, uout). The returned streams can be considered
433 may be copy of (uin, uout). The returned streams can be considered
414 "owned" in that print(), exec(), etc. never reach to them.
434 "owned" in that print(), exec(), etc. never reach to them.
415 """
435 """
416 uout.flush()
436 uout.flush()
417 fin, fout = uin, uout
437 fin, fout = uin, uout
418 if _testfileno(uin, stdin):
438 if _testfileno(uin, stdin):
419 newfd = os.dup(uin.fileno())
439 newfd = os.dup(uin.fileno())
420 nullfd = os.open(os.devnull, os.O_RDONLY)
440 nullfd = os.open(os.devnull, os.O_RDONLY)
421 os.dup2(nullfd, uin.fileno())
441 os.dup2(nullfd, uin.fileno())
422 os.close(nullfd)
442 os.close(nullfd)
423 fin = os.fdopen(newfd, 'rb')
443 fin = os.fdopen(newfd, 'rb')
424 if _testfileno(uout, stdout):
444 if _testfileno(uout, stdout):
425 newfd = os.dup(uout.fileno())
445 newfd = os.dup(uout.fileno())
426 os.dup2(stderr.fileno(), uout.fileno())
446 os.dup2(stderr.fileno(), uout.fileno())
427 fout = os.fdopen(newfd, 'wb')
447 fout = os.fdopen(newfd, 'wb')
428 return fin, fout
448 return fin, fout
429
449
430
450
431 def restorestdio(uin, uout, fin, fout):
451 def restorestdio(uin, uout, fin, fout):
432 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
452 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
433 uout.flush()
453 uout.flush()
434 for f, uif in [(fin, uin), (fout, uout)]:
454 for f, uif in [(fin, uin), (fout, uout)]:
435 if f is not uif:
455 if f is not uif:
436 os.dup2(f.fileno(), uif.fileno())
456 os.dup2(f.fileno(), uif.fileno())
437 f.close()
457 f.close()
438
458
439
459
440 def shellenviron(environ=None):
460 def shellenviron(environ=None):
441 """return environ with optional override, useful for shelling out"""
461 """return environ with optional override, useful for shelling out"""
442
462
443 def py2shell(val):
463 def py2shell(val):
444 """convert python object into string that is useful to shell"""
464 """convert python object into string that is useful to shell"""
445 if val is None or val is False:
465 if val is None or val is False:
446 return b'0'
466 return b'0'
447 if val is True:
467 if val is True:
448 return b'1'
468 return b'1'
449 return pycompat.bytestr(val)
469 return pycompat.bytestr(val)
450
470
451 env = dict(encoding.environ)
471 env = dict(encoding.environ)
452 if environ:
472 if environ:
453 env.update((k, py2shell(v)) for k, v in environ.items())
473 env.update((k, py2shell(v)) for k, v in environ.items())
454 env[b'HG'] = hgexecutable()
474 env[b'HG'] = hgexecutable()
455 return env
475 return env
456
476
457
477
458 if pycompat.iswindows:
478 if pycompat.iswindows:
459
479
460 def shelltonative(cmd, env):
480 def shelltonative(cmd, env):
461 return platform.shelltocmdexe( # pytype: disable=module-attr
481 return platform.shelltocmdexe( # pytype: disable=module-attr
462 cmd, shellenviron(env)
482 cmd, shellenviron(env)
463 )
483 )
464
484
465 tonativestr = encoding.strfromlocal
485 tonativestr = encoding.strfromlocal
466 else:
486 else:
467
487
468 def shelltonative(cmd, env):
488 def shelltonative(cmd, env):
469 return cmd
489 return cmd
470
490
471 tonativestr = pycompat.identity
491 tonativestr = pycompat.identity
472
492
473
493
474 def tonativeenv(env):
494 def tonativeenv(env):
475 """convert the environment from bytes to strings suitable for Popen(), etc."""
495 """convert the environment from bytes to strings suitable for Popen(), etc."""
476 return pycompat.rapply(tonativestr, env)
496 return pycompat.rapply(tonativestr, env)
477
497
478
498
479 def system(cmd, environ=None, cwd=None, out=None):
499 def system(cmd, environ=None, cwd=None, out=None):
480 """enhanced shell command execution.
500 """enhanced shell command execution.
481 run with environment maybe modified, maybe in different dir.
501 run with environment maybe modified, maybe in different dir.
482
502
483 if out is specified, it is assumed to be a file-like object that has a
503 if out is specified, it is assumed to be a file-like object that has a
484 write() method. stdout and stderr will be redirected to out."""
504 write() method. stdout and stderr will be redirected to out."""
485 try:
505 try:
486 stdout.flush()
506 stdout.flush()
487 except Exception:
507 except Exception:
488 pass
508 pass
489 env = shellenviron(environ)
509 env = shellenviron(environ)
490 if out is None or isstdout(out):
510 if out is None or isstdout(out):
491 rc = subprocess.call(
511 rc = subprocess.call(
492 tonativestr(cmd),
512 tonativestr(cmd),
493 shell=True,
513 shell=True,
494 close_fds=closefds,
514 close_fds=closefds,
495 env=tonativeenv(env),
515 env=tonativeenv(env),
496 cwd=pycompat.rapply(tonativestr, cwd),
516 cwd=pycompat.rapply(tonativestr, cwd),
497 )
517 )
498 else:
518 else:
499 proc = subprocess.Popen(
519 proc = subprocess.Popen(
500 tonativestr(cmd),
520 tonativestr(cmd),
501 shell=True,
521 shell=True,
502 close_fds=closefds,
522 close_fds=closefds,
503 env=tonativeenv(env),
523 env=tonativeenv(env),
504 cwd=pycompat.rapply(tonativestr, cwd),
524 cwd=pycompat.rapply(tonativestr, cwd),
505 stdout=subprocess.PIPE,
525 stdout=subprocess.PIPE,
506 stderr=subprocess.STDOUT,
526 stderr=subprocess.STDOUT,
507 )
527 )
508 for line in iter(proc.stdout.readline, b''):
528 for line in iter(proc.stdout.readline, b''):
509 out.write(line)
529 out.write(line)
510 proc.wait()
530 proc.wait()
511 rc = proc.returncode
531 rc = proc.returncode
512 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
532 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
513 rc = 0
533 rc = 0
514 return rc
534 return rc
515
535
516
536
517 _is_gui = None
537 _is_gui = None
518
538
519
539
520 def _gui():
540 def _gui():
521 '''Are we running in a GUI?'''
541 '''Are we running in a GUI?'''
522 if pycompat.isdarwin:
542 if pycompat.isdarwin:
523 if b'SSH_CONNECTION' in encoding.environ:
543 if b'SSH_CONNECTION' in encoding.environ:
524 # handle SSH access to a box where the user is logged in
544 # handle SSH access to a box where the user is logged in
525 return False
545 return False
526 elif getattr(osutil, 'isgui', None):
546 elif getattr(osutil, 'isgui', None):
527 # check if a CoreGraphics session is available
547 # check if a CoreGraphics session is available
528 return osutil.isgui()
548 return osutil.isgui()
529 else:
549 else:
530 # pure build; use a safe default
550 # pure build; use a safe default
531 return True
551 return True
532 else:
552 else:
533 return (
553 return (
534 pycompat.iswindows
554 pycompat.iswindows
535 or encoding.environ.get(b"DISPLAY")
555 or encoding.environ.get(b"DISPLAY")
536 or encoding.environ.get(b"WAYLAND_DISPLAY")
556 or encoding.environ.get(b"WAYLAND_DISPLAY")
537 )
557 )
538
558
539
559
540 def gui():
560 def gui():
541 global _is_gui
561 global _is_gui
542 if _is_gui is None:
562 if _is_gui is None:
543 _is_gui = _gui()
563 _is_gui = _gui()
544 return _is_gui
564 return _is_gui
545
565
546
566
547 def hgcmd():
567 def hgcmd():
548 """Return the command used to execute current hg
568 """Return the command used to execute current hg
549
569
550 This is different from hgexecutable() because on Windows we want
570 This is different from hgexecutable() because on Windows we want
551 to avoid things opening new shell windows like batch files, so we
571 to avoid things opening new shell windows like batch files, so we
552 get either the python call or current executable.
572 get either the python call or current executable.
553 """
573 """
554 if resourceutil.mainfrozen():
574 if resourceutil.mainfrozen():
555 if getattr(sys, 'frozen', None) == 'macosx_app':
575 if getattr(sys, 'frozen', None) == 'macosx_app':
556 # Env variable set by py2app
576 # Env variable set by py2app
557 return [encoding.environ[b'EXECUTABLEPATH']]
577 return [encoding.environ[b'EXECUTABLEPATH']]
558 else:
578 else:
559 return [pycompat.sysexecutable]
579 return [pycompat.sysexecutable]
560 return _gethgcmd()
580 return _gethgcmd()
561
581
562
582
563 def rundetached(args, condfn):
583 def rundetached(args, condfn):
564 """Execute the argument list in a detached process.
584 """Execute the argument list in a detached process.
565
585
566 condfn is a callable which is called repeatedly and should return
586 condfn is a callable which is called repeatedly and should return
567 True once the child process is known to have started successfully.
587 True once the child process is known to have started successfully.
568 At this point, the child process PID is returned. If the child
588 At this point, the child process PID is returned. If the child
569 process fails to start or finishes before condfn() evaluates to
589 process fails to start or finishes before condfn() evaluates to
570 True, return -1.
590 True, return -1.
571 """
591 """
572 # Windows case is easier because the child process is either
592 # Windows case is easier because the child process is either
573 # successfully starting and validating the condition or exiting
593 # successfully starting and validating the condition or exiting
574 # on failure. We just poll on its PID. On Unix, if the child
594 # on failure. We just poll on its PID. On Unix, if the child
575 # process fails to start, it will be left in a zombie state until
595 # process fails to start, it will be left in a zombie state until
576 # the parent wait on it, which we cannot do since we expect a long
596 # the parent wait on it, which we cannot do since we expect a long
577 # running process on success. Instead we listen for SIGCHLD telling
597 # running process on success. Instead we listen for SIGCHLD telling
578 # us our child process terminated.
598 # us our child process terminated.
579 terminated = set()
599 terminated = set()
580
600
581 def handler(signum, frame):
601 def handler(signum, frame):
582 terminated.add(os.wait())
602 terminated.add(os.wait())
583
603
584 prevhandler = None
604 prevhandler = None
585 SIGCHLD = getattr(signal, 'SIGCHLD', None)
605 SIGCHLD = getattr(signal, 'SIGCHLD', None)
586 if SIGCHLD is not None:
606 if SIGCHLD is not None:
587 prevhandler = signal.signal(SIGCHLD, handler)
607 prevhandler = signal.signal(SIGCHLD, handler)
588 try:
608 try:
589 pid = spawndetached(args)
609 pid = spawndetached(args)
590 while not condfn():
610 while not condfn():
591 if (pid in terminated or not testpid(pid)) and not condfn():
611 if (pid in terminated or not testpid(pid)) and not condfn():
592 return -1
612 return -1
593 time.sleep(0.1)
613 time.sleep(0.1)
594 return pid
614 return pid
595 finally:
615 finally:
596 if prevhandler is not None:
616 if prevhandler is not None:
597 signal.signal(signal.SIGCHLD, prevhandler)
617 signal.signal(signal.SIGCHLD, prevhandler)
598
618
599
619
600 @contextlib.contextmanager
620 @contextlib.contextmanager
601 def uninterruptible(warn):
621 def uninterruptible(warn):
602 """Inhibit SIGINT handling on a region of code.
622 """Inhibit SIGINT handling on a region of code.
603
623
604 Note that if this is called in a non-main thread, it turns into a no-op.
624 Note that if this is called in a non-main thread, it turns into a no-op.
605
625
606 Args:
626 Args:
607 warn: A callable which takes no arguments, and returns True if the
627 warn: A callable which takes no arguments, and returns True if the
608 previous signal handling should be restored.
628 previous signal handling should be restored.
609 """
629 """
610
630
611 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
631 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
612 shouldbail = []
632 shouldbail = []
613
633
614 def disabledsiginthandler(*args):
634 def disabledsiginthandler(*args):
615 if warn():
635 if warn():
616 signal.signal(signal.SIGINT, oldsiginthandler[0])
636 signal.signal(signal.SIGINT, oldsiginthandler[0])
617 del oldsiginthandler[0]
637 del oldsiginthandler[0]
618 shouldbail.append(True)
638 shouldbail.append(True)
619
639
620 try:
640 try:
621 try:
641 try:
622 signal.signal(signal.SIGINT, disabledsiginthandler)
642 signal.signal(signal.SIGINT, disabledsiginthandler)
623 except ValueError:
643 except ValueError:
624 # wrong thread, oh well, we tried
644 # wrong thread, oh well, we tried
625 del oldsiginthandler[0]
645 del oldsiginthandler[0]
626 yield
646 yield
627 finally:
647 finally:
628 if oldsiginthandler:
648 if oldsiginthandler:
629 signal.signal(signal.SIGINT, oldsiginthandler[0])
649 signal.signal(signal.SIGINT, oldsiginthandler[0])
630 if shouldbail:
650 if shouldbail:
631 raise KeyboardInterrupt
651 raise KeyboardInterrupt
632
652
633
653
634 if pycompat.iswindows:
654 if pycompat.iswindows:
635 # no fork on Windows, but we can create a detached process
655 # no fork on Windows, but we can create a detached process
636 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
656 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
637 # No stdlib constant exists for this value
657 # No stdlib constant exists for this value
638 DETACHED_PROCESS = 0x00000008
658 DETACHED_PROCESS = 0x00000008
639 # Following creation flags might create a console GUI window.
659 # Following creation flags might create a console GUI window.
640 # Using subprocess.CREATE_NEW_CONSOLE might helps.
660 # Using subprocess.CREATE_NEW_CONSOLE might helps.
641 # See https://phab.mercurial-scm.org/D1701 for discussion
661 # See https://phab.mercurial-scm.org/D1701 for discussion
642 _creationflags = (
662 _creationflags = (
643 DETACHED_PROCESS
663 DETACHED_PROCESS
644 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
664 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
645 )
665 )
646
666
647 def runbgcommand(
667 def runbgcommand(
648 script,
668 script,
649 env,
669 env,
650 shell=False,
670 shell=False,
651 stdout=None,
671 stdout=None,
652 stderr=None,
672 stderr=None,
653 ensurestart=True,
673 ensurestart=True,
654 record_wait=None,
674 record_wait=None,
655 stdin_bytes=None,
675 stdin_bytes=None,
656 ):
676 ):
657 '''Spawn a command without waiting for it to finish.'''
677 '''Spawn a command without waiting for it to finish.'''
658 # we can't use close_fds *and* redirect stdin. I'm not sure that we
678 # we can't use close_fds *and* redirect stdin. I'm not sure that we
659 # need to because the detached process has no console connection.
679 # need to because the detached process has no console connection.
660
680
661 try:
681 try:
662 stdin = None
682 stdin = None
663 if stdin_bytes is not None:
683 if stdin_bytes is not None:
664 stdin = pycompat.unnamedtempfile()
684 stdin = pycompat.unnamedtempfile()
665 stdin.write(stdin_bytes)
685 stdin.write(stdin_bytes)
666 stdin.flush()
686 stdin.flush()
667 stdin.seek(0)
687 stdin.seek(0)
668
688
669 p = subprocess.Popen(
689 p = subprocess.Popen(
670 pycompat.rapply(tonativestr, script),
690 pycompat.rapply(tonativestr, script),
671 shell=shell,
691 shell=shell,
672 env=tonativeenv(env),
692 env=tonativeenv(env),
673 close_fds=True,
693 close_fds=True,
674 creationflags=_creationflags,
694 creationflags=_creationflags,
675 stdin=stdin,
695 stdin=stdin,
676 stdout=stdout,
696 stdout=stdout,
677 stderr=stderr,
697 stderr=stderr,
678 )
698 )
679 if record_wait is not None:
699 if record_wait is not None:
680 record_wait(p.wait)
700 record_wait(p.wait)
681 finally:
701 finally:
682 if stdin is not None:
702 if stdin is not None:
683 stdin.close()
703 stdin.close()
684
704
685
705
686 else:
706 else:
687
707
688 def runbgcommand(
708 def runbgcommand(
689 cmd,
709 cmd,
690 env,
710 env,
691 shell=False,
711 shell=False,
692 stdout=None,
712 stdout=None,
693 stderr=None,
713 stderr=None,
694 ensurestart=True,
714 ensurestart=True,
695 record_wait=None,
715 record_wait=None,
696 stdin_bytes=None,
716 stdin_bytes=None,
697 ):
717 ):
698 """Spawn a command without waiting for it to finish.
718 """Spawn a command without waiting for it to finish.
699
719
700
720
701 When `record_wait` is not None, the spawned process will not be fully
721 When `record_wait` is not None, the spawned process will not be fully
702 detached and the `record_wait` argument will be called with a the
722 detached and the `record_wait` argument will be called with a the
703 `Subprocess.wait` function for the spawned process. This is mostly
723 `Subprocess.wait` function for the spawned process. This is mostly
704 useful for developers that need to make sure the spawned process
724 useful for developers that need to make sure the spawned process
705 finished before a certain point. (eg: writing test)"""
725 finished before a certain point. (eg: writing test)"""
706 if pycompat.isdarwin:
726 if pycompat.isdarwin:
707 # avoid crash in CoreFoundation in case another thread
727 # avoid crash in CoreFoundation in case another thread
708 # calls gui() while we're calling fork().
728 # calls gui() while we're calling fork().
709 gui()
729 gui()
710
730
711 if shell:
731 if shell:
712 script = cmd
732 script = cmd
713 else:
733 else:
714 if isinstance(cmd, bytes):
734 if isinstance(cmd, bytes):
715 cmd = [cmd]
735 cmd = [cmd]
716 script = b' '.join(shellquote(x) for x in cmd)
736 script = b' '.join(shellquote(x) for x in cmd)
717 if record_wait is None:
737 if record_wait is None:
718 # double-fork to completely detach from the parent process
738 # double-fork to completely detach from the parent process
719 script = b'( %s ) &' % script
739 script = b'( %s ) &' % script
720 start_new_session = True
740 start_new_session = True
721 else:
741 else:
722 start_new_session = False
742 start_new_session = False
723 ensurestart = True
743 ensurestart = True
724
744
725 stdin = None
745 stdin = None
726
746
727 try:
747 try:
728 if stdin_bytes is None:
748 if stdin_bytes is None:
729 stdin = subprocess.DEVNULL
749 stdin = subprocess.DEVNULL
730 else:
750 else:
731 stdin = pycompat.unnamedtempfile()
751 stdin = pycompat.unnamedtempfile()
732 stdin.write(stdin_bytes)
752 stdin.write(stdin_bytes)
733 stdin.flush()
753 stdin.flush()
734 stdin.seek(0)
754 stdin.seek(0)
735 if stdout is None:
755 if stdout is None:
736 stdout = subprocess.DEVNULL
756 stdout = subprocess.DEVNULL
737 if stderr is None:
757 if stderr is None:
738 stderr = subprocess.DEVNULL
758 stderr = subprocess.DEVNULL
739
759
740 p = subprocess.Popen(
760 p = subprocess.Popen(
741 script,
761 script,
742 shell=True,
762 shell=True,
743 env=env,
763 env=env,
744 close_fds=True,
764 close_fds=True,
745 stdin=stdin,
765 stdin=stdin,
746 stdout=stdout,
766 stdout=stdout,
747 stderr=stderr,
767 stderr=stderr,
748 start_new_session=start_new_session,
768 start_new_session=start_new_session,
749 )
769 )
750 except Exception:
770 except Exception:
751 if record_wait is not None:
771 if record_wait is not None:
752 record_wait(255)
772 record_wait(255)
753 raise
773 raise
754 finally:
774 finally:
755 if stdin_bytes is not None and stdin is not None:
775 if stdin_bytes is not None and stdin is not None:
756 assert not isinstance(stdin, int)
776 assert not isinstance(stdin, int)
757 stdin.close()
777 stdin.close()
758 if not ensurestart:
778 if not ensurestart:
759 # Even though we're not waiting on the child process,
779 # Even though we're not waiting on the child process,
760 # we still must call waitpid() on it at some point so
780 # we still must call waitpid() on it at some point so
761 # it's not a zombie/defunct. This is especially relevant for
781 # it's not a zombie/defunct. This is especially relevant for
762 # chg since the parent process won't die anytime soon.
782 # chg since the parent process won't die anytime soon.
763 # We use a thread to make the overhead tiny.
783 # We use a thread to make the overhead tiny.
764 t = threading.Thread(target=lambda: p.wait)
784 t = threading.Thread(target=lambda: p.wait)
765 t.daemon = True
785 t.daemon = True
766 t.start()
786 t.start()
767 else:
787 else:
768 returncode = p.wait
788 returncode = p.wait
769 if record_wait is not None:
789 if record_wait is not None:
770 record_wait(returncode)
790 record_wait(returncode)
General Comments 0
You need to be logged in to leave comments. Login now