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