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